[
  {
    "path": ".editorconfig",
    "content": "﻿[*.cs]\n\n# IDE0290: Use primary constructor\ndotnet_diagnostic.IDE0290.severity = none\n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n**/Properties/launchSettings.json\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Typescript v1 declaration files\ntypings/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# JetBrains Rider\n.idea/\n*.sln.iml\n\n# CodeRush\n.cr/\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\r\n/SizWizPlus/Resources/ClientSecret.json\r\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"wiki\"]\n\tpath = wiki\n\turl = https://github.com/maxim-zhao/SidWizPlus.wiki.git\n\tbranch = master\n"
  },
  {
    "path": "Directory.Build.props",
    "content": "<Project>\n    <PropertyGroup>\n        <LangVersion>latest</LangVersion>\n        <TreatWarningsAsErrors>true</TreatWarningsAsErrors>\n    </PropertyGroup>\n</Project>"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Maxim\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "LibSidWiz/Channel.cs",
    "content": "﻿using System;\r\nusing System.ComponentModel;\r\nusing System.ComponentModel.Design;\r\nusing System.Diagnostics.CodeAnalysis;\r\nusing System.Drawing;\r\nusing System.Drawing.Design;\r\nusing System.Globalization;\r\nusing System.IO;\r\nusing System.Linq;\r\nusing System.Reflection;\r\nusing System.Threading;\r\nusing System.Threading.Tasks;\r\nusing System.Windows.Forms;\r\nusing System.Windows.Forms.Design;\r\nusing LibSidWiz.Triggers;\r\nusing NAudio.Wave;\r\nusing Newtonsoft.Json;\r\nusing Newtonsoft.Json.Serialization;\r\n\r\nnamespace LibSidWiz\r\n{\r\n    /// <summary>\r\n    /// Wraps a single \"voice\", and also deals with loading the data into memory\r\n    /// </summary>\r\n    [SuppressMessage(\"ReSharper\", \"UnusedMember.Global\")]\r\n    public class Channel: IDisposable\r\n    {\r\n        private readonly bool _autoReloadOnSettingChanged;\r\n        private SampleBuffer _samples;\r\n        private SampleBuffer _samplesForTrigger;\r\n        private string _filename;\r\n        private string _externalTriggerFilename;\r\n        private ITriggerAlgorithm _algorithm;\r\n        private int _triggerLookaheadFrames; // Default to current frame only\r\n        private int _triggerLookaheadOnFailureFrames = 2; // Default to 2 frames ahead\r\n        private Color _lineColor = Color.White;\r\n        private string _label = \"\";\r\n        private float _lineWidth = 3;\r\n        private float _scale = 1.0f;\r\n        private int _viewWidthInSamples = 1500;\r\n        private Color _fillColor = Color.Transparent;\r\n        private float _zeroLineWidth;\r\n        private Color _zeroLineColor = Color.Transparent;\r\n        private Font _labelFont;\r\n        private Color _labelColor = Color.Transparent;\r\n        private Color _borderColor = Color.Transparent;\r\n        private float _borderWidth;\r\n        private ContentAlignment _labelAlignment = ContentAlignment.TopLeft;\r\n        private Padding _labelMargins = new(0, 0, 0, 0);\r\n        private bool _invertedTrigger;\r\n        private bool _borderEdges = true;\r\n        private Color _backgroundColor = Color.Transparent;\r\n        private bool _clip;\r\n        private Sides _side = Sides.Mix;\r\n        private bool _smoothLines = true;\r\n        private bool _filter;\r\n        private bool _renderIfSilent;\r\n        private double _fillBase;\r\n\r\n        public Channel(bool autoReloadOnSettingChanged)\r\n        {\r\n            _autoReloadOnSettingChanged = autoReloadOnSettingChanged;\r\n        }\r\n\r\n        public enum Sides\r\n        {\r\n            Left,\r\n            Right,\r\n            Mix\r\n        }\r\n\r\n        public event Action<Channel, bool> Changed;\r\n\r\n        public Task<bool> LoadDataAsync(CancellationToken token = new())\r\n        {\r\n            return Task.Factory.StartNew(() =>\r\n            {\r\n                try\r\n                {\r\n                    ErrorMessage = \"\";\r\n\r\n                    if (string.IsNullOrEmpty(Filename))\r\n                    {\r\n                        _samples = null;\r\n                        SampleCount = 0;\r\n                        Max = 0;\r\n                        Length = TimeSpan.Zero;\r\n                        Loading = false;\r\n                        IsEmpty = true;\r\n                        return false;\r\n                    }\r\n\r\n                    IsEmpty = false;\r\n                    Loading = true;\r\n\r\n                    Console.WriteLine($\"- Reading {Filename}\");\r\n                    _samples = new SampleBuffer(Filename, Side, HighPassFilter);\r\n                    SampleRate = _samples.SampleRate;\r\n                    Length = _samples.Length;\r\n\r\n                    token.ThrowIfCancellationRequested();\r\n\r\n                    _samples.Analyze();\r\n\r\n                    SampleCount = _samples.Count;\r\n\r\n                    token.ThrowIfCancellationRequested();\r\n\r\n                    Max = Math.Max(Math.Abs(_samples.Max), Math.Abs(_samples.Min));\r\n\r\n                    // This is a bit arbitrary. Nuked OPLL emits a DC offset of about 0.0008..0.0018 for an unused channel,\r\n                    // and a fade out (or low pass filter) will pull that down to 0.\r\n                    Console.WriteLine($\"- Sample range for {Filename} is {_samples.Min}..{_samples.Max} = range {Math.Abs(_samples.Max - _samples.Min)}\");\r\n                    IsSilent = Math.Abs(_samples.Max - _samples.Min) < SilenceThreshold;\r\n\r\n                    // Point at the same SampleBuffer\r\n                    _samplesForTrigger = string.IsNullOrEmpty(ExternalTriggerFilename) \r\n                        ? _samples \r\n                        : new SampleBuffer(ExternalTriggerFilename, Side, HighPassFilter);\r\n\r\n                    Loading = false;\r\n                    return true;\r\n                }\r\n                catch (TaskCanceledException)\r\n                {\r\n                    // Blank out if cancelled\r\n                    Max = 0;\r\n                    SampleRate = 0;\r\n                    Length = TimeSpan.Zero;\r\n                    if (_samplesForTrigger != _samples)\r\n                    {\r\n                        _samplesForTrigger?.Dispose();\r\n                    }\r\n                    _samplesForTrigger = null;\r\n                    _samples?.Dispose();\r\n                    _samples = null;\r\n                    Loading = false;\r\n                    return false;\r\n                }\r\n                catch (Exception ex)\r\n                {\r\n                    ErrorMessage = ex.ToString();\r\n                    Max = 0;\r\n                    SampleRate = 0;\r\n                    Length = TimeSpan.Zero;\r\n                    if (_samplesForTrigger != _samples)\r\n                    {\r\n                        _samplesForTrigger?.Dispose();\r\n                    }\r\n                    _samplesForTrigger = null;\r\n                    _samples?.Dispose();\r\n                    _samples = null;\r\n                    Loading = false;\r\n                    return false;\r\n                }\r\n                finally\r\n                {\r\n                    Changed?.Invoke(this, false);\r\n                }\r\n            }, token);\r\n        }\r\n\r\n        [Category(\"Data\")]\r\n        [Description(\"The full text of any error message when loading the file\")]\r\n        [JsonIgnore]\r\n        public string ErrorMessage { get; private set; }\r\n\r\n        [Category(\"Data\")]\r\n        [Editor(typeof(FileNameEditor), typeof(UITypeEditor))]\r\n        [Description(\"The filename to be rendered\")]\r\n        public string Filename\r\n        {\r\n            get => _filename;\r\n            set\r\n            {\r\n                bool needReload = value != _filename;\r\n                _filename = value;\r\n                Changed?.Invoke(this, needReload);\r\n                if (_filename != \"\" && string.IsNullOrEmpty(_label))\r\n                {\r\n                    Label = GuessNameFromMultidumperFilename(_filename);\r\n                }\r\n            }\r\n        }\r\n\r\n        [Category(\"Triggering\")]\r\n        [Editor(typeof(FileNameEditor), typeof(UITypeEditor))]\r\n        [Description(\"The filename to use for oscilloscope triggering. Leave blank to use the channel's sound data.\")]\r\n        public string ExternalTriggerFilename\r\n        {\r\n            get => _externalTriggerFilename;\r\n            set\r\n            {\r\n                bool needReload = value != _externalTriggerFilename;\r\n                _externalTriggerFilename = value;\r\n                // Change algorithm to RisingEdgeTrigger when using an external trigger\r\n                _algorithm = new RisingEdgeTrigger();\r\n                Changed?.Invoke(this, needReload);\r\n            }\r\n        }\r\n\r\n        [Category(\"Data\")]\r\n        [Description(\"The channel to use from the file (if stereo)\")]\r\n        public Sides Side\r\n        {\r\n            get => _side;\r\n            set\r\n            {\r\n                bool needReload = value != _side;\r\n                _side = value;\r\n                Changed?.Invoke(this, needReload);\r\n                if (_autoReloadOnSettingChanged)\r\n                {\r\n                    LoadDataAsync();\r\n                }\r\n            }\r\n        }\r\n\r\n        [Category(\"Data\")]\r\n        [Description(\"If enabled, high pass filtering will be used to remove DC offsets\")]\r\n        public bool HighPassFilter\r\n        {\r\n            get => _filter;\r\n            set\r\n            {\r\n                bool needReload = value != _filter;\r\n                _filter = value;\r\n                Changed?.Invoke(this, needReload);\r\n                if (_autoReloadOnSettingChanged)\r\n                {\r\n                    LoadDataAsync();\r\n                }\r\n            }\r\n        }\r\n\r\n        [Category(\"Data\")]\r\n        [Description(\"The amplitude range at which a channel is considered silent\")]\r\n        public float SilenceThreshold { get; set; } = 0.01f;\r\n\r\n        [Category(\"Triggering\")]\r\n        [Description(\"The algorithm to use for rendering\")]\r\n        [TypeConverter(typeof(TriggerAlgorithmTypeConverter))]\r\n        [JsonConverter(typeof(TriggerAlgorithmJsonConverter))]\r\n        public ITriggerAlgorithm Algorithm\r\n        {\r\n            get => _algorithm;\r\n            set\r\n            {\r\n                _algorithm = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Triggering\")]\r\n        [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.\")]\r\n        public int TriggerLookaheadFrames\r\n        {\r\n            get => _triggerLookaheadFrames;\r\n            set\r\n            {\r\n                _triggerLookaheadFrames = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Triggering\")]\r\n        [Description(\"How many frames to allow the triggering algorithm to look ahead, when nothing is found with the default lookahead.\")]\r\n        public int TriggerLookaheadOnFailureFrames\r\n        {\r\n            get => _triggerLookaheadOnFailureFrames;\r\n            set\r\n            {\r\n                _triggerLookaheadOnFailureFrames = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Appearance\")]\r\n        [Description(\"The line colour\")]\r\n        [Editor(typeof(MyColorEditor), typeof(UITypeEditor))]\r\n        [TypeConverter(typeof(MyColorConverter))]\r\n        public Color LineColor\r\n        {\r\n            get => _lineColor;\r\n            set\r\n            {\r\n                _lineColor = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Appearance\")]\r\n        [Description(\"The line width, in pixels. Fractional values are supported.\")]\r\n        public float LineWidth\r\n        {\r\n            get => _lineWidth;\r\n            set\r\n            {\r\n                _lineWidth = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Appearance\")]\r\n        [Description(\"The fill colour. Set to transparent to have no fill.\")]\r\n        [Editor(typeof(MyColorEditor), typeof(UITypeEditor))]\r\n        [TypeConverter(typeof(MyColorConverter))]\r\n        public Color FillColor\r\n        {\r\n            get => _fillColor;\r\n            set\r\n            {\r\n                _fillColor = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Appearance\")]\r\n        [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.\")]\r\n        public double FillBase\r\n        {\r\n            get => _fillBase;\r\n            set\r\n            {\r\n                _fillBase = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Appearance\")]\r\n        [Description(\"Whether to draw lines pixelated (false) or anti-aliased (true)\")]\r\n        public bool SmoothLines\r\n        {\r\n            get => _smoothLines;\r\n            set\r\n            {\r\n                _smoothLines = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Appearance\")]\r\n        [Description(\"The width of the zero line\")]\r\n        public float ZeroLineWidth\r\n        {\r\n            get => _zeroLineWidth;\r\n            set\r\n            {\r\n                _zeroLineWidth = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Appearance\")]\r\n        [Description(\"The color of the zero line\")]\r\n        [Editor(typeof(MyColorEditor), typeof(UITypeEditor))]\r\n        [TypeConverter(typeof(MyColorConverter))]\r\n        public Color ZeroLineColor\r\n        {\r\n            get => _zeroLineColor;\r\n            set\r\n            {\r\n                _zeroLineColor = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Appearance\")]\r\n        [Description(\"The color of the border\")]\r\n        [Editor(typeof(MyColorEditor), typeof(UITypeEditor))]\r\n        [TypeConverter(typeof(MyColorConverter))]\r\n        public Color BorderColor\r\n        {\r\n            get => _borderColor;\r\n            set\r\n            {\r\n                _borderColor = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Appearance\")]\r\n        [Description(\"The width of the border\")]\r\n        public float BorderWidth\r\n        {\r\n            get => _borderWidth;\r\n            set\r\n            {\r\n                _borderWidth = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Appearance\")]\r\n        [Description(\"Whether to draw the outer edges of any border boxes\")]\r\n        public bool BorderEdges\r\n        {\r\n            get => _borderEdges;\r\n            set\r\n            {\r\n                _borderEdges = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Appearance\")]\r\n        [Description(\"A background colour for the channel. This is layered above any background image, and can be transparent.\")]\r\n        [Editor(typeof(MyColorEditor), typeof(UITypeEditor))]\r\n        [TypeConverter(typeof(MyColorConverter))]\r\n        public Color BackgroundColor\r\n        {\r\n            get => _backgroundColor;\r\n            set\r\n            {\r\n                _backgroundColor = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Appearance\")]\r\n        [Description(\"The label for the channel\")]\r\n        [Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))]\r\n        public string Label\r\n        {\r\n            get => _label;\r\n            set\r\n            {\r\n                _label = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Appearance\")]\r\n        [Description(\"The font for the channel label\")]\r\n        public Font LabelFont\r\n        {\r\n            get => _labelFont;\r\n            set\r\n            {\r\n                _labelFont = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Appearance\")]\r\n        [Description(\"The color for the channel label\")]\r\n        [Editor(typeof(MyColorEditor), typeof(UITypeEditor))]\r\n        [TypeConverter(typeof(MyColorConverter))]\r\n        public Color LabelColor\r\n        {\r\n            get => _labelColor;\r\n            set\r\n            {\r\n                _labelColor = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Appearance\")]\r\n        [Description(\"The alignment for the channel label\")]\r\n        public ContentAlignment LabelAlignment\r\n        {\r\n            get => _labelAlignment;\r\n            set\r\n            {\r\n                _labelAlignment = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Appearance\")]\r\n        [Description(\"The margins for the chanel label\")]\r\n        public Padding LabelMargins\r\n        {\r\n            get => _labelMargins;\r\n            set\r\n            {\r\n                _labelMargins = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Adjustment\")]\r\n        [Description(\"Vertical scaling. This may be set by the auto-scaler.\")]\r\n        public float Scale\r\n        {\r\n            get => _scale;\r\n            set\r\n            {\r\n                _scale = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Adjustment\")]\r\n        [Description(\"Whether to constrain the waveform to its screen area when scaled past 100%\")]\r\n        public bool Clip\r\n        {\r\n            get => _clip;\r\n            set\r\n            {\r\n                _clip = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Adjustment\")]\r\n        [Description(\"View width, in ms\")]\r\n        [JsonIgnore]\r\n        public float ViewWidthInMilliseconds\r\n        {\r\n            get => SampleRate == 0 ? 0 : (float)_viewWidthInSamples * 1000 / SampleRate;\r\n            set\r\n            {\r\n                _viewWidthInSamples = (int) (value / 1000 * SampleRate);\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Adjustment\")]\r\n        [Description(\"View width, in samples\")]\r\n        public int ViewWidthInSamples\r\n        {\r\n            get => _viewWidthInSamples;\r\n            set\r\n            {\r\n                _viewWidthInSamples = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Triggering\")]\r\n        [Description(\"Set to true to trigger in the opposite direction\")]\r\n        // ReSharper disable once MemberCanBePrivate.Global\r\n        public bool InvertedTrigger\r\n        {\r\n            get => _invertedTrigger;\r\n            set\r\n            {\r\n                _invertedTrigger = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        [Category(\"Data\")]\r\n        [Description(\"Peak amplitude for the channel\")]\r\n        [JsonIgnore]\r\n        public float Max { get; private set; }\r\n\r\n        [Browsable(false)]\r\n        [JsonIgnore]\r\n        public long SampleCount { get; private set; }\r\n\r\n        [Category(\"Data\")]\r\n        [Description(\"Duration of the channel\")]\r\n        [JsonIgnore]\r\n        public TimeSpan Length { get; private set; }\r\n\r\n        [Category(\"Data\")]\r\n        [Description(\"Sampling rate of the channel\")]\r\n        [JsonIgnore]\r\n        public int SampleRate { get; private set; }\r\n\r\n        [Category(\"Appearance\")]\r\n        [Description(\"Whether to render silent channels normally. If false, a warning message is shown instead.\")]\r\n        public bool RenderIfSilent\r\n        {\r\n            get => _renderIfSilent;\r\n            set\r\n            {\r\n                _renderIfSilent = value;\r\n                Changed?.Invoke(this, false);\r\n            }\r\n        }\r\n\r\n        // ReSharper disable once CompareOfFloatsByEqualityOperator\r\n        [Browsable(false)]\r\n        [JsonIgnore]\r\n        public bool IsSilent { get; private set; }\r\n\r\n        [Browsable(false)]\r\n        [JsonIgnore]\r\n        public bool Loading { get; private set; } = true;\r\n\r\n        [Browsable(false)]\r\n        [JsonIgnore]\r\n        public bool IsEmpty { get; private set; }\r\n\r\n        [Browsable(false)]\r\n        [JsonIgnore]\r\n        internal Rectangle Bounds { get; set; }\r\n\r\n        internal float GetSample(int sampleIndex, bool forTrigger = true)\r\n        {\r\n            var source = forTrigger ? _samplesForTrigger : _samples;\r\n            return sampleIndex < 0 || sampleIndex >= source.Count ? 0 : source[sampleIndex] * Scale * (forTrigger && InvertedTrigger ? -1 : 1);\r\n        }\r\n\r\n        internal int GetTriggerPoint(int frameIndexSamples, int frameSamples, int previousTriggerPoint)\r\n        {\r\n            // Try at default settings\r\n            var result = Algorithm.GetTriggerPoint(this, frameIndexSamples, frameIndexSamples + frameSamples * (TriggerLookaheadFrames + 1), previousTriggerPoint);\r\n\r\n            if (result < frameIndexSamples)\r\n            {\r\n                // Try again\r\n                result = Algorithm.GetTriggerPoint(this, frameIndexSamples, frameIndexSamples + frameSamples * (TriggerLookaheadOnFailureFrames + 1), previousTriggerPoint);\r\n            }\r\n\r\n            if (result < frameIndexSamples)\r\n            {\r\n                // Default on failure\r\n                result = frameIndexSamples;\r\n            }\r\n\r\n            return result;\r\n        }\r\n\r\n        public static string GuessNameFromMultidumperFilename(string filename)\r\n        {\r\n            var namePart = Path.GetFileNameWithoutExtension(filename);\r\n            try\r\n            {\r\n                if (namePart == null)\r\n                {\r\n                    return filename;\r\n                }\r\n\r\n                var index = namePart.IndexOf(\" - YM2413 #\", StringComparison.Ordinal);\r\n                if (index > -1)\r\n                {\r\n                    index = int.Parse(namePart.Substring(index + 11));\r\n                    if (index < 9)\r\n                    {\r\n                        return $\"YM2413 Tone {index + 1}\";\r\n                    }\r\n\r\n                    switch (index)\r\n                    {\r\n                        case 9: return \"YM2413 Bass Drum\";\r\n                        case 10: return \"YM2413 Snare Drum\";\r\n                        case 11: return \"YM2413 Tom-Tom\";\r\n                        case 12: return \"YM2413 Cymbal\";\r\n                        case 13: return \"YM2413 Hi-Hat\";\r\n                    }\r\n                }\r\n\r\n                index = namePart.IndexOf(\" - SEGA PSG #\", StringComparison.Ordinal);\r\n                if (index > -1)\r\n                {\r\n                    if (int.TryParse(namePart.Substring(index + 13), out index))\r\n                    {\r\n                        switch (index)\r\n                        {\r\n                            case 0:\r\n                            case 1:\r\n                            case 2:\r\n                                return $\"Sega PSG Square {index + 1}\";\r\n                            case 3:\r\n                                return \"Sega PSG Noise\";\r\n                        }\r\n                    }\r\n                }\r\n\r\n                index = namePart.IndexOf(\" - SN76489 #\", StringComparison.Ordinal);\r\n                if (index > -1)\r\n                {\r\n                    if (int.TryParse(namePart.Substring(index + 12), out index))\r\n                    {\r\n                        switch (index)\r\n                        {\r\n                            case 0:\r\n                            case 1:\r\n                            case 2:\r\n                                return $\"SN76489 Square {index + 1}\";\r\n                            case 3:\r\n                                return \"SN76489 Noise\";\r\n                        }\r\n                    }\r\n                }\r\n\r\n                // Guess it's the bit after the last \" - \"\r\n                index = namePart.LastIndexOf(\" - \", StringComparison.Ordinal);\r\n                if (index > -1)\r\n                {\r\n                    return namePart.Substring(index + 3);\r\n                }\r\n            }\r\n            catch (Exception ex)\r\n            {\r\n                Console.Error.WriteLine($\"Error guessing channel name for {filename}: {ex}\");\r\n            }\r\n\r\n            // Default to just the filename\r\n            return namePart;\r\n        }\r\n\r\n        /// <summary>\r\n        /// This allows us to use a property grid to select a trigger algorithm\r\n        /// </summary>\r\n        // ReSharper disable once MemberCanBePrivate.Global\r\n        public class TriggerAlgorithmTypeConverter: StringConverter\r\n        {\r\n            public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)\r\n            {\r\n                return true;\r\n            }\r\n\r\n            public override bool GetStandardValuesSupported(ITypeDescriptorContext context)\r\n            {\r\n                return true;\r\n            }\r\n\r\n            public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)\r\n            {\r\n                return new StandardValuesCollection(\r\n                    Assembly.GetExecutingAssembly()\r\n                        .GetTypes()\r\n                        .Where(t => typeof(ITriggerAlgorithm).IsAssignableFrom(t) && t != typeof(ITriggerAlgorithm))\r\n                        .Select(t => t.Name)\r\n                        .ToList());\r\n            }\r\n\r\n            public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)\r\n            {\r\n                return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);\r\n            }\r\n\r\n            public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)\r\n            {\r\n                if (value is string)\r\n                {\r\n                    var type = Assembly.GetExecutingAssembly()\r\n                        .GetTypes()\r\n                        .FirstOrDefault(t => typeof(ITriggerAlgorithm).IsAssignableFrom(t) && t.Name.ToLowerInvariant().Equals(value.ToString().ToLowerInvariant()));\r\n                    if (type != null)\r\n                    {\r\n                        return Activator.CreateInstance(type) as ITriggerAlgorithm;\r\n                    }\r\n                }\r\n\r\n                return base.ConvertFrom(context, culture, value);\r\n            }\r\n        }\r\n\r\n        // ReSharper disable once MemberCanBePrivate.Global\r\n        public class TriggerAlgorithmJsonConverter: JsonConverter<ITriggerAlgorithm>\r\n        {\r\n            public override void WriteJson(JsonWriter writer, ITriggerAlgorithm value, JsonSerializer serializer)\r\n            {\r\n                writer.WriteValue(value.GetType().Name);\r\n            }\r\n\r\n            public override ITriggerAlgorithm ReadJson(JsonReader reader, Type objectType, ITriggerAlgorithm existingValue, bool hasExistingValue, JsonSerializer serializer)\r\n            {\r\n                var type = Assembly.GetExecutingAssembly()\r\n                    .GetTypes()\r\n                    .FirstOrDefault(t => \r\n                        typeof(ITriggerAlgorithm).IsAssignableFrom(t) && \r\n                        t.Name.ToLowerInvariant().Equals(reader.Value?.ToString().ToLowerInvariant()));\r\n                if (type != null)\r\n                {\r\n                    return Activator.CreateInstance(type) as ITriggerAlgorithm;\r\n                }\r\n\r\n                return existingValue;\r\n            }\r\n        }\r\n\r\n        public void Dispose()\r\n        {\r\n            _samples?.Dispose();\r\n            if (_samplesForTrigger != _samples)\r\n            {\r\n                _samplesForTrigger.Dispose();\r\n            }\r\n            _labelFont?.Dispose();\r\n        }\r\n\r\n        public string ToJson()\r\n        {\r\n            return JsonConvert.SerializeObject(this, new JsonSerializerSettings\r\n            {\r\n                Formatting = Formatting.Indented,\r\n            });\r\n        }\r\n\r\n        public void FromJson(string json, bool preserveSource)\r\n        {\r\n            if (preserveSource)\r\n            {\r\n                JsonConvert.PopulateObject(json, this, new JsonSerializerSettings\r\n                {\r\n                    ContractResolver = new PreservingContractResolver()\r\n                });\r\n            }\r\n            else\r\n            {\r\n                JsonConvert.PopulateObject(json, this);\r\n            }\r\n        }\r\n\r\n        private class PreservingContractResolver : DefaultContractResolver\r\n        {\r\n            protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)\r\n            {\r\n                var property = base.CreateProperty(member, memberSerialization);\r\n                if (property.PropertyName == nameof(Filename) ||\r\n                    property.PropertyName == nameof(Label) ||\r\n                    property.PropertyName == nameof(ExternalTriggerFilename))\r\n                {\r\n                    property.Ignored = true;\r\n                }\r\n                return property;\r\n            }\r\n        }\r\n\r\n        public bool IsMono()\r\n        {\r\n            if (Side == Sides.Left || Side == Sides.Right)\r\n            {\r\n                return true;\r\n            }\r\n\r\n            using var reader = new WaveFileReader(_filename);\r\n            var sp = reader.ToSampleProvider().ToStereo();\r\n            if (sp.WaveFormat.Channels == 1)\r\n            {\r\n                return true;\r\n            }\r\n\r\n            int bufferSize = sp.WaveFormat.SampleRate * 10;\r\n            var buffer = new float[bufferSize];\r\n            sp.Read(buffer, 0, bufferSize);\r\n            for (int i = 0; i < bufferSize; i += 2)\r\n            {\r\n                // ReSharper disable once CompareOfFloatsByEqualityOperator\r\n                if (buffer[i] != buffer[i + 1])\r\n                {\r\n                    return false;\r\n                }\r\n            }\r\n\r\n            return true;\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "LibSidWiz/Extensions.cs",
    "content": "﻿using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Text.RegularExpressions;\r\n\r\nnamespace LibSidWiz\r\n{\r\n    public static class Extensions\r\n    {\r\n        public static IOrderedEnumerable<T> OrderByAlphaNumeric<T>(this IEnumerable<T> source, Func<T, string> selector)\r\n        {\r\n            // Materialise the collection if necessary\r\n            var list = source.ToList();\r\n            // Find the longest sequence of digits\r\n            var max = list\r\n                .SelectMany(i => Regex\r\n                    .Matches(selector(i), @\"\\d+\")\r\n                    .Cast<Match>()\r\n                    .Select(m => (int?)m.Value.Length))\r\n                .Max() ?? 0;\r\n\r\n            // Pad all number sequences to that length, then order by this padded string\r\n            return list.OrderBy(i => Regex.Replace(selector(i), @\"\\d+\", m => m.Value.PadLeft(max, '0')));\r\n        }\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "LibSidWiz/LibSidWiz.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\r\n  <PropertyGroup>\r\n    <TargetFramework>net48</TargetFramework>\r\n    <UseWPF>true</UseWPF>\r\n    <UseWindowsForms>true</UseWindowsForms>\r\n    <LangVersion>latest</LangVersion>\r\n    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>\r\n  </PropertyGroup>\r\n  <ItemGroup>\r\n    <Reference Include=\"Microsoft.CSharp\" />\r\n    <Reference Include=\"NReplayGain\">\r\n      <HintPath>..\\NReplayGain.dll</HintPath>\r\n    </Reference>\r\n    <Reference Include=\"System.Design\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <PackageReference Include=\"Cyotek.Windows.Forms.ColorPicker\" Version=\"1.7.2\" />\r\n    <PackageReference Include=\"Microsoft-WindowsAPICodePack-Shell\" Version=\"1.1.5\" />\r\n    <PackageReference Include=\"NAudio\" Version=\"2.2.1\" />\r\n    <PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.3\" />\r\n    <PackageReference Include=\"SkiaSharp\" Version=\"2.88.8\" />\r\n    <PackageReference Include=\"SkiaSharp.Views.WindowsForms\" Version=\"2.88.8\" />\r\n  </ItemGroup>\r\n</Project>"
  },
  {
    "path": "LibSidWiz/Mixer.cs",
    "content": "﻿using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing NAudio.Wave;\r\nusing NAudio.Wave.SampleProviders;\r\nusing NReplayGain;\r\n\r\nnamespace LibSidWiz\r\n{\r\n    /// <summary>\r\n    /// Deals with mixing audio to a \"master file\"\r\n    /// </summary>\r\n    public static class Mixer\r\n    {\r\n        public static void MixToFile(IList<Channel> channels, string filename, bool applyReplayGain)\r\n        {\r\n            Console.WriteLine(\"Mixing per-channel data...\");\r\n\r\n            // We make new readers...\r\n            var readers = new List<WaveFileReader>();\r\n            try\r\n            {\r\n                readers.AddRange(channels\r\n                    .Where(c => !string.IsNullOrEmpty(c.Filename))\r\n                    .Select(c => new WaveFileReader(c.Filename)));\r\n\r\n                if (applyReplayGain)\r\n                {\r\n                    Console.WriteLine(\"Computing ReplayGain...\");\r\n                    // We read it in a second at a time, to calculate Replay Gains\r\n                    var mixer = new MixingSampleProvider(readers.Select(x => x.ToSampleProvider().ToStereo()));\r\n                    const int sampleRate = 44100;\r\n                    var resampler = new WdlResamplingSampleProvider(mixer, sampleRate);\r\n                    // We use a 1s buffer...\r\n                    var buffer = new float[sampleRate * 2]; // *2 for stereo\r\n                    var replayGain = new TrackGain(sampleRate);\r\n                    for (;;)\r\n                    {\r\n                        int numRead = resampler.Read(buffer, 0, buffer.Length);\r\n                        if (numRead == 0)\r\n                        {\r\n                            break;\r\n                        }\r\n\r\n                        // And analyze\r\n                        replayGain.AnalyzeSamples(buffer, numRead);\r\n                    }\r\n\r\n                    // The +3 is to make it at \"YouTube loudness\", which is a lot louder than ReplayGain defaults to.\r\n                    // TODO make this configurable?\r\n                    var gain = replayGain.GetGain() + 3;\r\n\r\n                    Console.WriteLine($\"Applying ReplayGain ({gain:N} dB) and saving to {filename}\");\r\n\r\n                    // Reset the readers\r\n                    foreach (var reader in readers)\r\n                    {\r\n                        reader.Position = 0;\r\n                    }\r\n\r\n                    // We make a new mixer just in case resetting the previous one is problematic...\r\n                    mixer = new MixingSampleProvider(readers.Select(x => x.ToSampleProvider().ToStereo()));\r\n                    var amplifier = new VolumeSampleProvider(mixer) {Volume = (float) Math.Pow(10, gain / 20)};\r\n                    WaveFileWriter.CreateWaveFile(filename, amplifier.ToWaveProvider());\r\n                }\r\n                else\r\n                {\r\n                    var mixer = new MixingSampleProvider(readers.Select(x => x.ToSampleProvider().ToStereo()));\r\n                    WaveFileWriter.CreateWaveFile(filename, mixer.ToWaveProvider());\r\n                }\r\n            }\r\n            finally\r\n            {\r\n                foreach (var reader in readers)\r\n                {\r\n                    reader.Dispose();\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "LibSidWiz/MultiDumperWrapper.cs",
    "content": "﻿using System;\r\nusing System.Collections.Generic;\r\nusing System.Diagnostics;\r\nusing System.IO;\r\nusing System.Linq;\r\nusing System.Text;\r\nusing System.Text.RegularExpressions;\r\nusing Newtonsoft.Json;\r\nusing Newtonsoft.Json.Linq;\r\n\r\nnamespace LibSidWiz\r\n{\r\n    public class MultiDumperWrapper: IDisposable\r\n    {\r\n        private readonly string _multiDumperPath;\r\n        private readonly int _samplingRate;\r\n        private readonly int _loopCount;\r\n        private readonly int _fadeMs;\r\n        private readonly int _gapMs;\r\n        private readonly string _extraOptions;\r\n        private ProcessWrapper _processWrapper;\r\n        private readonly HashSet<string> _allowedParameters;\r\n\r\n        public MultiDumperWrapper(string multiDumperPath, int samplingRate, int loopCount, int fadeMs, int gapMs, string extraOptions)\r\n        {\r\n            _multiDumperPath = multiDumperPath;\r\n            _samplingRate = samplingRate;\r\n            _loopCount = loopCount;\r\n            _fadeMs = fadeMs;\r\n            _gapMs = gapMs;\r\n            _extraOptions = extraOptions;\r\n\r\n            // We parse the usage info first to check for allowed parameters, in the form --name or --name=\r\n            var helpText = GetOutputText(\"\", true);\r\n            _allowedParameters =\r\n            [\r\n                ..Regex.Matches(helpText, \"--[^= ]+\")\r\n                    .Cast<Match>()\r\n                    .Select(x => x.Value)\r\n            ];\r\n        }\r\n\r\n        public class Song\r\n        {\r\n            // ReSharper disable UnusedAutoPropertyAccessor.Global\r\n            public string Name { get; set; }\r\n            public string Author { get; set; }\r\n            public string Comment { get; set; }\r\n            public string Copyright { get; set; }\r\n            public string Dumper { get; set; }\r\n            public string Game { get; set; }\r\n            public string System { get; set; }\r\n            public List<string> Channels { get; set; }\r\n            public int Index { get; set; }\r\n            public string Filename { get; set; }\r\n            public TimeSpan IntroLength { get; set; }\r\n            public TimeSpan LoopLength { get; set; }\r\n            // ReSharper restore UnusedAutoPropertyAccessor.Global\r\n            public int LoopCount { get; set; }\r\n            public TimeSpan ForceLength { get; set; } = TimeSpan.Zero;\r\n\r\n            public override string ToString()\r\n            {\r\n                var length = GetLength();\r\n\r\n                var sb = new StringBuilder();\r\n                sb.Append($\"#{Index}: \")\r\n                    .Append(string.IsNullOrWhiteSpace(Game) ? \"Unknown game\" : Game)\r\n                    .Append(\" - \")\r\n                    .Append(string.IsNullOrWhiteSpace(Name) ? \"Unknown title\" : Name)\r\n                    .Append(\" - \")\r\n                    .Append(string.IsNullOrWhiteSpace(Author) ? \"Unknown author\" : Author)\r\n                    .Append(string.IsNullOrWhiteSpace(Comment) ? \"\" : $\" ({Comment})\")\r\n                    .Append(length <= TimeSpan.Zero ? \" (Unknown length)\" : $\" ({length})\");\r\n                return sb.ToString();\r\n            }\r\n\r\n            public TimeSpan GetLength()\r\n            {\r\n                if (ForceLength > TimeSpan.Zero)\r\n                {\r\n                    return ForceLength;\r\n                }\r\n\r\n                var length = TimeSpan.Zero;\r\n                if (IntroLength > TimeSpan.Zero)\r\n                {\r\n                    length += IntroLength;\r\n                }\r\n\r\n                if (LoopLength > TimeSpan.Zero)\r\n                {\r\n                    length += TimeSpan.FromTicks(LoopLength.Ticks * LoopCount);\r\n                }\r\n\r\n                return length;\r\n            }\r\n        }\r\n\r\n        public IEnumerable<Song> GetSongs(string filename)\r\n        {\r\n            filename = Path.GetFullPath(filename);\r\n\r\n            if (!File.Exists(filename))\r\n            {\r\n                throw new FileNotFoundException(\"Cannot find VGM file\", filename);\r\n            }\r\n\r\n            // Don't check the --json parameter as it may not have mentioned it for an old build\r\n            var json = GetOutputText($\"\\\"{filename}\\\" --json\", false);\r\n\r\n            if (string.IsNullOrEmpty(json))\r\n            {\r\n                throw new Exception(\"Failed to get song data from MultiDumper\");\r\n            }\r\n\r\n            // Extract metadata\r\n            // Example result:\r\n            // {\r\n            //  \"channels\":[\r\n            //      \"SEGA PSG #0\",\"SEGA PSG #1\",\"SEGA PSG #2\",\"SEGA PSG #3\",\r\n            //      \"YM2413 #0\",\"YM2413 #1\",\"YM2413 #2\",\"YM2413 #3\",\"YM2413 #4\",\"YM2413 #5\",\"YM2413 #6\",\r\n            //      \"YM2413 #7\",\"YM2413 #8\",\"YM2413 #9\",\"YM2413 #10\",\"YM2413 #11\",\"YM2413 #12\",\"YM2413 #13\"],\r\n            //  \"containerinfo\":\r\n            //  {\r\n            //      \"copyright\":\"1988/08/14\",\r\n            //      \"dumper\":\"sherpa\",\r\n            //      \"game\":\"Golvellius - Valley of Doom\",\r\n            //      \"system\":\"Sega Master System\"\r\n            //  },\r\n            //  \"songs\":[\r\n            //      {\r\n            //          \"author\":\"Masatomo Miyamoto, Takeshi Santo, Shin-kun, Pazu\",\r\n            //          \"comment\":\"\",\r\n            //          \"name\":\"Title Screen\",\r\n            //          \"length\":\"12345\"\r\n            //      }],\r\n            //  \"subsongCount\":1\r\n            // }\r\n            // or:\r\n            // { \"error\": \"some error message\" }\r\n            dynamic metadata = JsonConvert.DeserializeObject(json) \r\n                ?? throw new Exception(\"Failed to parse song metadata\");\r\n\r\n            if (metadata[\"error\"] != null)\r\n            {\r\n                throw new Exception($\"Failed to parse song metadata: {metadata.error}\");\r\n            }\r\n\r\n            var channels = metadata.channels.ToObject<List<string>>();\r\n            var songs = (JArray)metadata.songs;\r\n            var i = 0;\r\n\r\n            return songs.Cast<dynamic>().Select(s => new Song\r\n            {\r\n                Filename = filename,\r\n                Index = i++,\r\n                Name = Clean(s.name),\r\n                Author = Clean(s.author),\r\n                Channels = channels,\r\n                Comment = Clean(s.comment),\r\n                Copyright = Clean(metadata.containerinfo.copyright),\r\n                Dumper = Clean(metadata.containerinfo.dumper),\r\n                Game = Clean(metadata.containerinfo.game),\r\n                System = Clean(metadata.containerinfo.system),\r\n                IntroLength = TimeSpan.FromMilliseconds((int)(s.intro_length ?? 0)),\r\n                LoopLength = TimeSpan.FromMilliseconds((int)(s.loop_length ?? 0)),\r\n                LoopCount = _loopCount\r\n            });\r\n\r\n            // This helps us reject any junk strings MultiDumper gives us for empty tags\r\n            static string Clean(string s) => string.IsNullOrEmpty(s) || s.Any(char.IsControl) ? string.Empty : s;\r\n        }\r\n\r\n        private string GetOutputText(string args, bool includeStdErr)\r\n        {\r\n            using var p = new ProcessWrapper(\r\n                _multiDumperPath,\r\n                args,\r\n                includeStdErr);\r\n            string text = string.Join(\"\", p.Lines());\r\n            // Try to decode any UTF-8 in there\r\n            try\r\n            {\r\n                text = Encoding.UTF8.GetString(Encoding.Default.GetBytes(text));\r\n            }\r\n            catch (Exception)\r\n            {\r\n                // Ignore it, use unfixed string\r\n            }\r\n            return text;\r\n        }\r\n\r\n        public IEnumerable<string> Dump(Song song, Action<double> onProgress)\r\n        {\r\n            var args = new StringBuilder($\"\\\"{song.Filename}\\\" {song.Index}\");\r\n            AddArgIfSupported(args, \"sampling_rate\", _samplingRate);\r\n            AddArgIfSupported(args, \"fade_length\", _fadeMs);\r\n            AddArgIfSupported(args, \"loop_count\", _loopCount);\r\n            AddArgIfSupported(args, \"gap_length\", _gapMs);\r\n            if (song.ForceLength > TimeSpan.Zero)\r\n            {\r\n                AddArgIfSupported(args, \"play_length\", (long)song.ForceLength.TotalMilliseconds);\r\n            }\r\n\r\n            if (!string.IsNullOrEmpty(_extraOptions))\r\n            {\r\n                args.Append($\" {_extraOptions}\");\r\n            }\r\n\r\n            _processWrapper = new ProcessWrapper(\r\n                _multiDumperPath,\r\n                args.ToString(),\r\n                showConsole:true);\r\n            var progressParts = Enumerable.Repeat(0.0, song.Channels.Count).ToList();\r\n            var r = new Regex(@\"(?<channel>\\d+)\\|(?<position>\\d+)\\|(?<total>\\d+)\");\r\n            var stopwatch = Stopwatch.StartNew();\r\n            foreach (var match in _processWrapper.Lines().Select(l => r.Match(l)).Where(m => m.Success))\r\n            {\r\n                var channel = Convert.ToInt32(match.Groups[\"channel\"].Value);\r\n                if (channel < 0 || channel > song.Channels.Count)\r\n                {\r\n                    continue;\r\n                }\r\n\r\n                var position = Convert.ToDouble(match.Groups[\"position\"].Value);\r\n                var total = Convert.ToDouble(match.Groups[\"total\"].Value);\r\n                progressParts[channel] = position / total;\r\n                if (stopwatch.Elapsed.TotalMilliseconds > 100)\r\n                {\r\n                    // Update the progress every 100ms\r\n                    onProgress?.Invoke(progressParts.Average());\r\n                    stopwatch.Restart();\r\n                }\r\n            }\r\n            _processWrapper.Dispose();\r\n            _processWrapper = null;\r\n\r\n            onProgress?.Invoke(1.0);\r\n\r\n            var baseName = Path.Combine(\r\n                Path.GetDirectoryName(song.Filename) ?? \"\",\r\n                Path.GetFileNameWithoutExtension(song.Filename));\r\n            return song.Channels.Select(channel => $\"{baseName} - {channel}.wav\");\r\n        }\r\n\r\n        private void AddArgIfSupported(StringBuilder args, string name, object value)\r\n        {\r\n            if (_allowedParameters.Contains($\"--{name}\"))\r\n            {\r\n                args.Append($\" --{name}={value}\");\r\n            }\r\n            else\r\n            {\r\n                Console.Error.WriteLine($\"Arg not supported: {name}\");\r\n            }\r\n        }\r\n\r\n        public void Dispose()\r\n        {\r\n            _processWrapper?.Dispose();\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "LibSidWiz/MyColorConverter.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Drawing;\n\nnamespace LibSidWiz;\n\n/// <summary>\n/// This overrides ColorConverter which interferes with the colour editor.\n/// </summary>\npublic class MyColorConverter : ColorConverter\n{\n    public override bool GetStandardValuesSupported(ITypeDescriptorContext context)\n    {\n        return false;\n    }\n}"
  },
  {
    "path": "LibSidWiz/MyColorEditor.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Drawing;\nusing System.Drawing.Design;\nusing System.Windows.Forms;\nusing System.Windows.Forms.Design;\n\nnamespace LibSidWiz;\n\n/// <summary>\n/// This allows us to implement a custom colour picker\n/// </summary>\npublic class MyColorEditor : UITypeEditor\n{\n    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)\n    {\n        return UITypeEditorEditStyle.Modal;\n    }\n\n    public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)\n    {\n        if (value is not Color c || provider == null)\n        {\n            return value;\n        }\n\n        var svc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));\n\n        if (svc == null)\n        {\n            return c;\n        }\n\n        using var form = new Cyotek.Windows.Forms.ColorPickerDialog();\n        form.Color = c;\n        form.ShowAlphaChannel = true;\n        return svc.ShowDialog(form) == DialogResult.OK ? form.Color : c;\n    }\n\n    public override bool GetPaintValueSupported(ITypeDescriptorContext context)\n    {\n        return true;\n    }\n\n    public override void PaintValue(PaintValueEventArgs e)\n    {\n        // This is just for the little rectangle on the left\n        // TODO: indicate transparency?\n        var color = (Color)e.Value;\n        if (color.A < 255)\n        {\n            // Draw a checkerboard of 4x4 using the system colours\n            e.Graphics.FillRectangle(SystemBrushes.Window, e.Bounds);\n            for (var x = 0; x < e.Bounds.Width; x += 4)\n            for (var y = 0; y < e.Bounds.Height; y += 4)\n                if (((x/4 ^ y/4) & 1) == 0)\n                {\n                    e.Graphics.FillRectangle(SystemBrushes.WindowText, x, y, 4, 4);\n                }\n        }\n        using (var brush = new SolidBrush(color))\n        {\n            e.Graphics.FillRectangle(brush, e.Bounds);\n        }\n\n        e.Graphics.DrawRectangle(SystemPens.WindowText, e.Bounds);\n    }\n}"
  },
  {
    "path": "LibSidWiz/Outputs/FfmpegOutput.cs",
    "content": "﻿using System;\r\nusing System.Diagnostics;\r\nusing System.IO;\r\nusing System.Text;\r\nusing SkiaSharp;\r\n\r\nnamespace LibSidWiz.Outputs\r\n{\r\n    public class FfmpegOutput : IGraphicsOutput\r\n    {\r\n        private readonly bool _throwStandardError;\r\n        private readonly Process _process;\r\n        private readonly BinaryWriter _writer;\r\n\r\n        public FfmpegOutput(string pathToExe, string filename, int width, int height, int fps, string extraArgs, string masterAudioFilename, string videoCodec, string audioCodec, bool throwStandardError)\r\n        {\r\n            _throwStandardError = throwStandardError;\r\n            // Build the FFMPEG commandline\r\n            var arguments = \"-y -hide_banner\"; // Overwrite, don't show banner at startup\r\n\r\n            // Audio input\r\n            if (File.Exists(masterAudioFilename))\r\n            {\r\n                arguments += $\" -i \\\"{masterAudioFilename}\\\"\";\r\n            }\r\n\r\n            // Video input\r\n            arguments += $\" -f rawvideo -pixel_format bgr0 -video_size {width}x{height} -framerate {fps} -i pipe:\";\r\n            \r\n            // Audio output\r\n            arguments += $\" -codec:a {audioCodec}\";\r\n\r\n            // Video output\r\n            arguments += $\" -codec:v {videoCodec} -movflags +faststart\";\r\n\r\n            // Extra args\r\n            arguments += $\" {extraArgs} \\\"{filename}\\\"\";\r\n\r\n            Console.WriteLine($\"Starting FFMPEG: {pathToExe} {arguments}\");\r\n\r\n            // We don't want a BOM to be injected if the system code page is set to UTF-8.\r\n            // This fails sometimes, so we swallow the error...\r\n            try\r\n            {\r\n                Console.InputEncoding = Encoding.ASCII;\r\n            }\r\n            catch (Exception e)\r\n            {\r\n                Console.WriteLine($\"Failed to change console encoding to ASCII. You may get video corruption. Exception said: {e.Message}\");\r\n            }\r\n\r\n            // Start it up\r\n            _process = Process.Start(\r\n                new ProcessStartInfo\r\n                {\r\n                    FileName = pathToExe,\r\n                    Arguments = arguments,\r\n                    UseShellExecute = false,\r\n                    RedirectStandardInput = true,\r\n                    RedirectStandardError = throwStandardError,\r\n                    RedirectStandardOutput = false,\r\n                    CreateNoWindow = throwStandardError // makes it inline in console mode\r\n                }\r\n            );\r\n\r\n            if (_process == null)\r\n            {\r\n                throw new Exception($\"Couldn't start FFMPEG with commandline {pathToExe} {arguments}\");\r\n            }\r\n\r\n            _writer = new BinaryWriter(_process.StandardInput.BaseStream);\r\n        }\r\n\r\n        public void Write(SKImage _, byte[] data, double __, TimeSpan ___)\r\n        {\r\n            try\r\n            {\r\n                _writer.Write(data);\r\n            }\r\n            catch (Exception e)\r\n            {\r\n                if (_throwStandardError)\r\n                {\r\n                    if (_process.HasExited)\r\n                    {\r\n                        throw new Exception(\r\n                            $\"\"\"\r\n                             FFMPEG has exited unexpectedly.\r\n                             Exit code was {_process.ExitCode}.\r\n                             Standard error was:\r\n                             {_process.StandardError.ReadToEnd()}\r\n                             \"\"\", e);\r\n                    }\r\n\r\n                    throw new Exception(\r\n                        $\"\"\"\r\n                         Cannot write to FFMPEG.\r\n                         Standard error was:\r\n                         {_process.StandardError.ReadToEnd()}\r\n                         \"\"\", e);\r\n                }\r\n\r\n                throw;\r\n            }\r\n        }\r\n\r\n        public void Dispose()\r\n        {\r\n            // This triggers a shutdown\r\n            _process?.StandardInput.BaseStream.Close();\r\n            if (_throwStandardError)\r\n            {\r\n                _process?.StandardError.Close();\r\n            }\r\n            // And we wait for it to finish...\r\n            _process?.WaitForExit();\r\n            _process?.Dispose();\r\n            _writer?.Dispose();\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "LibSidWiz/Outputs/IGraphicsOutput.cs",
    "content": "﻿using System;\r\nusing SkiaSharp;\r\n\r\nnamespace LibSidWiz.Outputs\r\n{\r\n    public interface IGraphicsOutput: IDisposable\r\n    {\r\n        void Write(SKImage image, byte[] data, double fractionComplete, TimeSpan length);\r\n    }\r\n}\r\n"
  },
  {
    "path": "LibSidWiz/Outputs/PreviewOutput.cs",
    "content": "﻿using System;\r\nusing System.Diagnostics;\r\nusing System.Windows.Forms;\r\nusing Microsoft.WindowsAPICodePack.Taskbar;\r\nusing SkiaSharp;\r\nusing SkiaSharp.Views.Desktop;\r\n\r\nnamespace LibSidWiz.Outputs\r\n{\r\n    public class PreviewOutput : IGraphicsOutput\r\n    {\r\n        private readonly int _frameSkip;\r\n        private readonly bool _pumpMessageQueue;\r\n        private readonly PreviewOutputForm _form;\r\n        private int _frameIndex;\r\n        private readonly Stopwatch _stopwatch;\r\n        private TimeSpan _lastFpsUpdateTime = TimeSpan.Zero;\r\n\r\n        public PreviewOutput(int frameSkip, bool pumpMessageQueue = false)\r\n        {\r\n            _frameSkip = frameSkip;\r\n            _pumpMessageQueue = pumpMessageQueue;\r\n            _form = new PreviewOutputForm();\r\n            _form.Show();\r\n            _form.SetDesktopLocation(0, 0);\r\n            _stopwatch = Stopwatch.StartNew();\r\n        }\r\n\r\n        public void Write(SKImage image, byte[] data, double fractionComplete, TimeSpan length)\r\n        {\r\n            if (!_form.Visible)\r\n            {\r\n                throw new Exception(\"Preview window closed\");\r\n            }\r\n\r\n            // Post-increment so we take frame 0\r\n            var showFrame = _frameIndex++ % _frameSkip == 0;\r\n            var showFps = showFrame || (_stopwatch.Elapsed - _lastFpsUpdateTime).TotalMilliseconds > 100;\r\n            if (showFps)\r\n            {\r\n                var elapsedSeconds = _stopwatch.Elapsed.TotalSeconds;\r\n                var fps = _frameIndex / elapsedSeconds;\r\n                var eta = TimeSpan.FromSeconds(elapsedSeconds / fractionComplete - elapsedSeconds);\r\n                _form.BeginInvoke(new Action(() =>\r\n                {\r\n                    if (_form.IsDisposed || !_form.Visible)\r\n                    {\r\n                        return;\r\n                    }\r\n                    _form.toolStripStatusLabel2.Text = $\"{fractionComplete:P} of {length} @ {fps:F}fps, ETA {eta:g}\";\r\n                    TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.Normal, _form.Handle);\r\n                    TaskbarManager.Instance.SetProgressValue((int)(fractionComplete * 100), 100, _form.Handle);\r\n                }));\r\n                _lastFpsUpdateTime = _stopwatch.Elapsed;\r\n            }\r\n\r\n            if (showFrame)\r\n            {\r\n                // Copy the bitmap for use on the GUI thread\r\n                var copy = image.ToBitmap();\r\n                _form.BeginInvoke(new Action(() =>\r\n                {\r\n                    if (_form.IsDisposed || !_form.Visible)\r\n                    {\r\n                        return;\r\n                    }\r\n\r\n                    _form.pictureBox1.Image = copy;\r\n                }));\r\n            }\r\n\r\n            if (_pumpMessageQueue)\r\n            {\r\n                Application.DoEvents();\r\n            }\r\n        }\r\n\r\n        public void Dispose()\r\n        {\r\n            _stopwatch.Stop();\r\n            try\r\n            {\r\n                _form?.BeginInvoke(() =>\r\n                    {\r\n                        _form?.Close();\r\n                        _form?.Dispose();\r\n                    }\r\n                );\r\n            }\r\n            catch (Exception)\r\n            {\r\n                // We might get this if exiting the program\r\n            }\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "LibSidWiz/Outputs/PreviewOutputForm.Designer.cs",
    "content": "﻿namespace LibSidWiz.Outputs\n{\n    partial class PreviewOutputForm\n    {\n        /// <summary>\n        /// Required designer variable.\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// Clean up any resources being used.\n        /// </summary>\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows Form Designer generated code\n\n        /// <summary>\n        /// Required method for Designer support - do not modify\n        /// the contents of this method with the code editor.\n        /// </summary>\n        private void InitializeComponent()\n        {\n            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PreviewOutputForm));\r\n            this.pictureBox1 = new System.Windows.Forms.PictureBox();\r\n            this.statusStrip1 = new System.Windows.Forms.StatusStrip();\r\n            this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel();\r\n            this.toolStripStatusLabel2 = new System.Windows.Forms.ToolStripStatusLabel();\r\n            ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();\r\n            this.statusStrip1.SuspendLayout();\r\n            this.SuspendLayout();\r\n            // \r\n            // pictureBox1\r\n            // \r\n            this.pictureBox1.Dock = System.Windows.Forms.DockStyle.Fill;\r\n            this.pictureBox1.Location = new System.Drawing.Point(0, 0);\r\n            this.pictureBox1.Name = \"pictureBox1\";\r\n            this.pictureBox1.Size = new System.Drawing.Size(368, 103);\r\n            this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;\r\n            this.pictureBox1.TabIndex = 3;\r\n            this.pictureBox1.TabStop = false;\r\n            // \r\n            // statusStrip1\r\n            // \r\n            this.statusStrip1.ImageScalingSize = new System.Drawing.Size(32, 32);\r\n            this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {\r\n            this.toolStripStatusLabel1,\r\n            this.toolStripStatusLabel2});\r\n            this.statusStrip1.Location = new System.Drawing.Point(0, 103);\r\n            this.statusStrip1.Name = \"statusStrip1\";\r\n            this.statusStrip1.Size = new System.Drawing.Size(368, 22);\r\n            this.statusStrip1.TabIndex = 4;\r\n            this.statusStrip1.Text = \"Ready!\";\r\n            // \r\n            // toolStripStatusLabel1\r\n            // \r\n            this.toolStripStatusLabel1.Name = \"toolStripStatusLabel1\";\r\n            this.toolStripStatusLabel1.Size = new System.Drawing.Size(0, 17);\r\n            // \r\n            // toolStripStatusLabel2\r\n            // \r\n            this.toolStripStatusLabel2.Name = \"toolStripStatusLabel2\";\r\n            this.toolStripStatusLabel2.Size = new System.Drawing.Size(39, 17);\r\n            this.toolStripStatusLabel2.Text = \"Ready\";\r\n            // \r\n            // PreviewOutputForm\r\n            // \r\n            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);\r\n            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;\r\n            this.AutoSize = true;\r\n            this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;\r\n            this.ClientSize = new System.Drawing.Size(368, 125);\r\n            this.Controls.Add(this.pictureBox1);\r\n            this.Controls.Add(this.statusStrip1);\r\n            this.Icon = ((System.Drawing.Icon)(resources.GetObject(\"$this.Icon\")));\r\n            this.Name = \"PreviewOutputForm\";\r\n            ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();\r\n            this.statusStrip1.ResumeLayout(false);\r\n            this.statusStrip1.PerformLayout();\r\n            this.ResumeLayout(false);\r\n            this.PerformLayout();\r\n\r\n        }\n\n        #endregion\n\n        public System.Windows.Forms.PictureBox pictureBox1;\n        private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel1;\n        public System.Windows.Forms.StatusStrip statusStrip1;\n        public System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel2;\n\n\n\n\n\n\n    }\n}"
  },
  {
    "path": "LibSidWiz/Outputs/PreviewOutputForm.cs",
    "content": "﻿using System.Windows.Forms;\r\n\r\nnamespace LibSidWiz.Outputs\r\n{\r\n    public partial class PreviewOutputForm : Form\r\n    {\r\n        \r\n        public PreviewOutputForm()\r\n        {\r\n            InitializeComponent();\r\n        }\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "LibSidWiz/Outputs/PreviewOutputForm.resx",
    "content": "﻿<?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    The primary goals of this format is to allow a simple XML format \r\n    that is mostly human readable. The generation and parsing of the \r\n    various data types are done through the TypeConverter classes \r\n    associated with the data types.\r\n    \r\n    Example:\r\n    \r\n    ... ado.net/XML headers & schema ...\r\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\r\n    <resheader name=\"version\">2.0</resheader>\r\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\r\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\r\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\r\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\r\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\r\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\r\n    </data>\r\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\r\n        <comment>This is a comment</comment>\r\n    </data>\r\n                \r\n    There are any number of \"resheader\" rows that contain simple \r\n    name/value pairs.\r\n    \r\n    Each data row contains a name, and value. The row also contains a \r\n    type or mimetype. Type corresponds to a .NET class that support \r\n    text/value conversion through the TypeConverter architecture. \r\n    Classes that don't support this are serialized and stored with the \r\n    mimetype set.\r\n    \r\n    The mimetype is used for serialized objects, and tells the \r\n    ResXResourceReader how to depersist the object. This is currently not \r\n    extensible. For a given mimetype the value must be set accordingly:\r\n    \r\n    Note - application/x-microsoft.net.object.binary.base64 is the format \r\n    that the ResXResourceWriter will generate, however the reader can \r\n    read any of the formats listed below.\r\n    \r\n    mimetype: application/x-microsoft.net.object.binary.base64\r\n    value   : The object must be serialized with \r\n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\r\n            : and then encoded with base64 encoding.\r\n    \r\n    mimetype: application/x-microsoft.net.object.soap.base64\r\n    value   : The object must be serialized with \r\n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\r\n            : and then encoded with base64 encoding.\r\n\r\n    mimetype: application/x-microsoft.net.object.bytearray.base64\r\n    value   : The object must be serialized into a byte array \r\n            : using a System.ComponentModel.TypeConverter\r\n            : and then encoded with base64 encoding.\r\n    -->\r\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\r\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\r\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\r\n      <xsd:complexType>\r\n        <xsd:choice maxOccurs=\"unbounded\">\r\n          <xsd:element name=\"metadata\">\r\n            <xsd:complexType>\r\n              <xsd:sequence>\r\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\r\n              </xsd:sequence>\r\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\r\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\r\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\r\n              <xsd:attribute ref=\"xml:space\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n          <xsd:element name=\"assembly\">\r\n            <xsd:complexType>\r\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\r\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n          <xsd:element name=\"data\">\r\n            <xsd:complexType>\r\n              <xsd:sequence>\r\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\r\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\r\n              </xsd:sequence>\r\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\r\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\r\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\r\n              <xsd:attribute ref=\"xml:space\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n          <xsd:element name=\"resheader\">\r\n            <xsd:complexType>\r\n              <xsd:sequence>\r\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\r\n              </xsd:sequence>\r\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n        </xsd:choice>\r\n      </xsd:complexType>\r\n    </xsd:element>\r\n  </xsd:schema>\r\n  <resheader name=\"resmimetype\">\r\n    <value>text/microsoft-resx</value>\r\n  </resheader>\r\n  <resheader name=\"version\">\r\n    <value>2.0</value>\r\n  </resheader>\r\n  <resheader name=\"reader\">\r\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\r\n  </resheader>\r\n  <resheader name=\"writer\">\r\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\r\n  </resheader>\r\n  <metadata name=\"statusStrip1.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\r\n    <value>17, 17</value>\r\n  </metadata>\r\n  <assembly alias=\"System.Drawing\" name=\"System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\" />\r\n  <data name=\"$this.Icon\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n    <value>\r\n        AAABAAUAEBAAAAEAIABoBAAAVgAAABgYAAABACAAiAkAAL4EAAAgIAAAAQAgAKgQAABGDgAAMDAAAAEA\r\n        IACoJQAA7h4AADAwAAABACAAqCUAAJZEAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAASCwAAEgsAAAAA\r\n        AAAAAAAAANbW/wDi4v8A3Nz/ANzc/wDj4/8A3Nz/ANDQ/wCtrP8A2tn/AMfJ/1d6u/+tq///q6n//01w\r\n        uP8AaWy9AAAAEwDY2P8A5ub/AN7e/wDe3v8A2dn/AMDA/wDb2v8Ri6L/EJyy/xicsv9+gc//nZ3w/62t\r\n        /v99gM3/QEmIqQAAAAsA19f/ANHR/QCbm9AAkJDMAGpqtwBTUqQAhYPcXmuw9FtmrP9FWo36SUd7pElJ\r\n        htFubrv8TU10mEJBa3UCAgkMAHd3nABcXF0AICAZAAAAEAYCDiIxNmp9MDd00UA/lvRHRpz/WFeX+Cws\r\n        ZoIsLFydVFSQ0BgYIRoYGCQAAAAAAAAAAAAAAAAAPT2XACIiXDBhYa7HcnK+/WVlnP+QkLL/bW2X/09P\r\n        nf9vb8X/Y2O0/zY2Y32Tk/8AAAAAAAAAAAAAAAAAAAAAAAAAAApkZJ6ydHTG/w8Ptf85OZX/pqa8/2lp\r\n        nf8AAMn/EhK5/3Bwxv9NTZ6wAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAdgYG54kxMof84N5n/OzqR/wAA\r\n        xP8KCqz/T0+k/zc2mf9KSqH/YmK54gAAAB0AAAAAAAAAAAAAAAAAAAAAAAAAEnV0r8uJitT/bnaV/2Zv\r\n        kf8qM5L/NT6K/3uEnf9yd5f/iIjV/1hYr8sAAAASAAAAAAAAAAA0NE0ALy9BKTg4ZpIoJ1iYhInY+GWH\r\n        3f9Yf9b/W4LV/1uC1v9WftT/d5Dm/4KC3PgoKFeYOTlmkhUVQSkZGU0ARkZnADc3TVlERIP/MDBp7nd4\r\n        uPtmbaX/Q0t1/5We7v+Aic//Nz9j/4eM1P9dXbr7Oztp7kREg/8WFk1ZISFnAD09WgA0NEhBQkJ60hsb\r\n        QZB8fLnqbm6h/x0dKv+bm+P/kpHV/x8fLv9wcKf/YWG96iUlQZBCQnrSFxdIQR0dWgAAAAAABAQGBAwM\r\n        ERUAAAEhgYG94WRkkv8xMUj/o6Pv/4qKy/8mJjj/goLB/2Rkv+EAAAEhDAwRFQQEBgQAAAAAAAAACAAA\r\n        AAAAAAAAAAAAEmBgp8OTk+j/kZHh/56e9P+bm/H/jo7b/46O6f9JSafDAAAAEgAAAAAAAAAAAAAAAEVF\r\n        k5AMDC4SEhI5AAMDHBBDQ5CtQkKM2C0tYLwuLmO6MTFquSwsXcUlJVOpCwslSzAwpwAAAAAAAAAAAAAA\r\n        AACPj+b8XFymthkZSUFTU6Sba2u6/i8vXZcTEzNdJiZYjgEBAx8AAAAeAAAAFQAAAAcAAAABAAAAAAAA\r\n        AAAAAAAAdHSpv52d8P9tbcvtlJTt+5+f7v9wcLT6W1uk7lhYl6kAAAAKAAAAAAAAAAAAAAAAAAAAAAAA\r\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAADgBwAAwAMAAMADAADAAwAAgAEAAIABAACAAQAAgAEAAGAH\r\n        AAAgDwAAAAcAAAB/AAAoAAAAGAAAADAAAAABACAAAAAAAAAJAAASCwAAEgsAAAAAAAAAAAAAAMfH/wDW\r\n        1v8A1dX/AMnJ/wDc3P8Aycn/ANXV/wDV1f8Aysr/ANTU/wCJif8AwcH/ANjY/wDKyv8Azsv/JFmI/5KP\r\n        8/+urv//rq7//5CO8/8fVYj/AJWS/wAkJHIAPz8AANvb/wD19f8A9fX/AN/f/wD///8A39//APX1/wD1\r\n        9f8A39//AP///wDNzP8AmJb/ANva/wDk4/8Dtbz/Z3K7/7Gw//+urv//rq7//6Sj/v9Vab3/CUldwAAF\r\n        ABkAFxQAAMnJ/wDZ2f8A2dn/AM3N/wDj4/8Azs7/AM/P/wDS0v8AnZ3/ANbW/wC/wP8dX47/HWeW/wC0\r\n        s/8ncJf/jYnX/35+0v+jo/T/sLD//6Sl7/98e8D/XVu26A8PKkogH00AANfX/wDu7v8A7e3/ALi4/ACY\r\n        mOkAsrL8AHFx8gBeXtUAOTnKAMTD/Q1eavOEgtj/hIPZ/xBDV/9fa67/UVB/yjQ0ddliYqX9enrN/3p6\r\n        vPArKz6ZUlKB0B0dMk0tLU0AAMHB/wCxsfIAh4fZAEFBfwAeHjEALCx2AB4eTgAAABAAKypeAFJOthQX\r\n        KKVoaMb/aGjF/zY0af9sa6f3GBghWgAAExkUFC+yUFCi/0dHc7EAAAAPAAAAFQAAAAMAAAAAADAwggAq\r\n        Kk4AAAAXAAAAAwAAAAAAAAAAAAAACw4PKUowMG+1OjeM7Tk5ju87O5v/Ojqb/1BQl/9QUJH9KSlvsggI\r\n        KXk4OHG+bm6r9h0dKE4wMEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsLKQAAAAwXMDBwpm1t\r\n        vPSUlOX/c3O2/2Vlm/9qap//Zmab/1JShf+EhMf/hobk/25u1v9MTJb/Pj5frwAAAAsAAAAAAAAAAAAA\r\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwxMXGmh4fm/35+wf8zM47/ISFi/8zMxv/p6eX/zMzF/xsb\r\n        df8PD57/NTWM/39/wf+Dg+L/JiZorwAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALy9GABkZ\r\n        JU57e7v0l5fd/xgYjv8AAPD/Dg6d/3Nzjf+Dg5//c3OL/w4Onf8AAP//AADt/xgYjv+Tk93/W1u79AgI\r\n        JU4WFkYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARERjACcnOH+cnOL/Xl6h/wICj/8bG5z/GxuJ/wMD\r\n        o/8AAOT/AADS/w8Pjv8eHp7/Ghqb/wICj/9fX6H/eXni/w8POH8gIGMAAAAAAAAAAAAAAAAAAAAAAAAA\r\n        AAAAAAAARERjACcnOH+bm+H/enrF/yQkXv+xsKj/sbGo/xYVdf8AAMT/AACo/2NidP/LysL/r6+n/yQk\r\n        Xv97e8X/eHjh/w8POH8gIGMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDBGABkZJU16ernzr6///3R5\r\n        sf9aZY7/XGaQ/0VPgf9BS4f/QUuE/1Bahv9gapT/XWaP/3x9tf+rq///WVm58wgIJU0XF0YAAAAAAAAA\r\n        AAAAAAAAAAAAABwcKQAWFh8ZJSU7dg4OI1UxMXKjjo3q/oOZ7f9Uf9j/VoHZ/1aB2f9Xgdj/V4HY/1aB\r\n        2f9VgNn/X4Xc/6Wq/P+Hh+r+KipyoxoaI1UfHzt2CAgfGQ0NKQAAAAAAAAAAAD8/XAAnJzh1XV2Y/yoq\r\n        cOULCxqhX1+U9JCf9P9igtP/W3rH/2uK3/9ri+D/bIvh/2OC1P9aecf/co7i/56i//8+PZT0DAwaoU1N\r\n        cOVISJj/EBA4dR4eXAAAAAAAAAAAAEREYwAsLD5/TU2A/y4ud/9HR4//Xl6X/6io9v89PFj/GBci/3x7\r\n        tf+ysf//p6bz/z08WP8YFyL/fXy1/6am//85OZb/R0eP/1RUd/81NYD/ExM+fyAgYwAAAAAAAAAAAD8/\r\n        XAAnJzh1XV2Y/ykpcOYMDBmYXV2M8Ken9v9XV4H/BgYI/1ZWf/+xsf//pKTy/1dXgf8GBgj/V1d//6en\r\n        //83N4vwDAwZmE1NcOZISJj/EBA4dR4eXAAAAAAAAAAAABwcKQAWFh8ZJSU6dxUVM0kAAAAdYmKQ46am\r\n        9f9XV4H/BgYI/1ZWf/+xsf//pKTy/1dXgf8GBgj/V1d//6en//85OY/jAAAAHSMjM0kfHzp3CAgfGQ0N\r\n        KQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcY2OQ46en9v87O1f/FhYh/3p6tP+wsP//pKTy/zs7\r\n        V/8WFiH/e3u0/6en//86Oo/jAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n        AAAAAAAYS0uI2Kam/v+oqPL/n5/p/6+v/f+wsP//sLD//6en8/+fn+j/r6/8/46O/v8xMYjYAAAAGAAA\r\n        AAAAAAAAAAAAAAAAAAAAAAAADAwuRAAAAAIAAAAAAAAAAAAAAACiov8AExNCYUxMm/d0dNv/amq8/3Fx\r\n        1P9pabr/cXHU/2pqvP9ubsn/Y2Oz/DY2jOgUFEVYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU1Ov5hcX\r\n        SFYAAAAAAAAAAAsLKgAAAAUWODiFqFxcuP0YGEKyBQUXrwsLK4sGBhiuCwssjgcHGa8JCSGhBgYWpAMD\r\n        ClUAAAA0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApaX9/19fuuccHEN/AwMXFwAAAxU1NYCqd3fS/0hI\r\n        eOoICBM5AAAAMQsLKFYICB18AAAAEAAAADAAAAAhAAAAMQAAAAMAAAANAAAAAAAAAAAAAAAAAAAAAAAA\r\n        AAAAAAAAhYXD8qio+f+Cgtv+MjJ7szIye7OJien+j4/Y/1dXqfw6OnTaEhI6k1hYt+hBQYDMAAAAFAAA\r\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALi5EbIWFw/OwsP//iIjs/4iI\r\n        7P+urv//r6///6io9P9+fr7/Y2Ot/3t7wvUzM0dhAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n        AAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAABAAAAAQAMAA8A+AAPAPAADwDwAA8A8AAPAPAA\r\n        DwDwAA8AwAADAMAAAwDAAAMAwAADAMAAAwD4AB8A+AAfADwAPwA4AD8AAAA/AAAH/wAAB/8AKAAAACAA\r\n        AABAAAAAAQAgAAAAAAAAEAAAEgsAABILAAAAAAAAAAAAAADAwP8AwcH/AM3N/wDJyf8Au7v/AM3N/wDN\r\n        zf8Au7v/AMnJ/wDNzf8AwcH/AMHB/wDOzv8AhYX/AImJ/wDPz/8Azc3/ALu7/wDJyf8AxsT/Clhv/1xb\r\n        yP+qqv//ra3//62t//+srP//bm3c/xBKbv8At7X/AEND5wAAABgAAAAAAN/f/wDi4v8A////APX1/wDR\r\n        0f8A////AP///wDR0f8A9fX/AP///wDi4v8A4uL/AP///wDy8v8AfHz/AMDA/wD///8A0dH/APf3/wDY\r\n        1/80SXv/pKL//66u//+trf//ra3//6ys//91dOD/F057/wCHhfYAKSmAAAAABQAAAAAA39//AOLi/wD/\r\n        //8A9fX/ANHR/wD///8A////ANHR/wD19f8A////AOLi/wDi4v8A////APf3/wDLy/8AaWj/ALOy/wDT\r\n        0/8A9/f/CG56/3h11v+wsP//rq7//62t//+trf//ra3//62t//94d+H/HidhzwAAACYAAAAAAAAAAADD\r\n        w/8AxMT/ANLS/wDNzf8Avb3/ANXV/wDV1f8Avr7/AMXF/wDNzf8Avb3/AHp6/wDFxf8A0tH/AY2T/yxH\r\n        lP8sRpP/AZCV/wCmo/80UJH/oZ7v/2hoqf+BgeX/qKj1/7Cw//+xsf//h4fD/3l5tf95eeP/Hh5RuQAA\r\n        AA4AAAAAANnZ/wDc3P8A+fn/AO7u/wDPz/8Aubn/ALm5/wDR0f8AjIz/AGZm/wBgYN8AICDfAN/f/wCt\r\n        qv8kOGP/lJH3/5SR9/8kPWf/Bl5v/3Nwvf9jY5LfLCxx30tLkf97e8P/g4PQ/56e+f9aWoTgGxsnrmpq\r\n        oPgzM1O5AAAADgAAAAAA4OD/AOHh/wDo6P8A1NT9AF5e6wA2NocANzeHAE5O7AA2NrYAAABZAAAAOAAr\r\n        K5YApqb4AC4rlD89XuyoqP//qan//zg3Vf8xMGD/iYna/xoaJJUAAAA0EhI5jCsrUPgoKG7/dnbZ/xsb\r\n        I5kAAAAGAAAAUQAAAB8AAAAAAAAAAACRkf8AiYn3ADg4rwAjI5kADw8lAAAABwAAAAgAAAAZAAAAEAAA\r\n        AAAAEBAAAAcHWgAREMIAAABtFxc97j09pf89PaT/Hh5O/3l52P9KSmr0AAAAVwgIDAAAAAAFDAwfrnNz\r\n        1/9KSnDqAAAAJQ8PFgAAAAAAAAAAAAAAAAAAAAAAAAAAWQAAAFAAAAAIAAAAAAAAAAAAAAAAAAAAAAAA\r\n        AAAAAAAIAgIPJQ4OIpg0NIThQD+p/0JCq/9CQqv/QUGq/0FBqv9DQ6f/Tk56/0NDlf8wMIbgCAgkpgQE\r\n        FaVAQI3gfn6//yUlNYoAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n        AAAAAAAAAAAABRISO4I4OG/rd3fY/aWl+P+Tk+P/aGiq/2hoqv9oaKr/aGiq/2dnpv9zc6r/paX2/5KS\r\n        9v9paeb/TEyx/1BQjv9UVH3hAAAAOQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n        AAAAAAAAAAAAAAAAAAcODjZ9Vla7+qSk/v+hoef/YGCV/yQkRP9ra2r/qamo/6WlpP+np6X/ODhE/xER\r\n        Z/87O27/Xl6S/6Ki5/+lpf7/UlKz/wsLJa4AAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAUEtLmvmnp///fn68/xgYYv8FBaX/AACO/6ampf//////////////\r\n        //9ZWXH/AADV/wAA3/8FBaD/GBhi/35+vP+jo///Ojqd+QAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA42NlC2mZnh/6ur+v82Nmz/AADX/wAA//8AALD/OjpV/1tb\r\n        c/9ZWXD/WVlw/x8fUP8AAOj/AAD//wAA//8AANf/NjZs/6io+v9ycuH/GRlQtgAAAA4AAAAAAAAAAAAA\r\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGEREYuezs///dnat/wQEfv8AAKz/AACl/wAA\r\n        m/8AAJL/AADZ/wAA4v8AAOL/AACt/wAAoP8AAKT/AACk/wAArP8EBH7/eXmt/5OT//8fH2LnAAAAGAAA\r\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYQkJi57Gx//92dq7/AACB/zs7\r\n        Vf+urqr/rq6r/zw8U/8AAOH/AAD+/wAA/v8QEGj/nJyU/62tq/+urqr/OztV/wAAgf95ea7/kZH//x4e\r\n        YucAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABhDQ2Lnr6///6mp\r\n        +P9UVIv/Pz9D/62sq/+trav/PDtG/wAAaP8AAHf/AAB3/xAQOf+cm5j/rKyr/62sq/8/P0P/VFSL/6ur\r\n        +P+Pj///Hx9i5wAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCQk\r\n        NYqBgcT/sbH//6eo9v9kbKX/WmSa/1pkm/9cZZz/XWab/11mm/9dZpv/XWac/1tkm/9aZJv/WmSa/29y\r\n        q/+pqff/q6v//1paxP8QEDWKAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAA\r\n        AFEAAABNAAAAQTU1hOGSkvn/pan8/1qD2/9Rftf/UX/X/1F/1/9Rf9f/UX/X/1F/1/9Rf9f/UX/X/1F/\r\n        1/9Qftf/j5/x/7Gw//+Rkfj/Ly+E4QAAAEEAAABNAAAAUQAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAEhI\r\n        agAYGCN1YmKQ+0BAkPsICCB7AAAKXFBPkfSlqfz/Vn/W/0991f9Rf9j/TnzU/0160f9Ne9L/TXrR/017\r\n        0v9Rftf/UX7X/0x60v+Nne//m5r//zk5kPQAAApcFhYge2JikPtAQJD7CQkjdSIiagAAAAAAAAAAAAAA\r\n        AAAAAAAAX1+JAB8fLaZnZ5X/NDSW/yAgP/clJVPmPz9i/Kyt//+cpPj/cny8/1tkmv+Fjtj/mqT4/5mi\r\n        9f+Zo/b/lJ7u/2Jspf9ibKX/lJ7u/6is/P+Ojf//HR1i/CcnU+YrKz/3aGiW/zQ0lf8ODi2mLS2JAAAA\r\n        AAAAAAAAAAAAAAAAAABdXYkAHx8upl5eiv8qKoj/QEBu/1tbyf9BQW7/sbH//4aFxP8TExv/AAAA/zIy\r\n        Sv+lpPL/rq7//6+v//9cXIf/AgID/wMDBP9eXor/sbH//46O//8hIW7/XV3J/0tLbv9dXYj/LCyK/w8P\r\n        LqYsLIkAAAAAAAAAAAAAAAAAAAAAAF1diQAdHSqmdHSq/0JCrP8QECngCAgWrj8/Xvexsf//hYXE/zU1\r\n        Tv8FBQf/EBAX/52d5/+urv//rq7//2Njkv8lJTb/AAAA/zw8Wf+xsf//jo7//x0dXvcLCxauHBwp4HV1\r\n        rP9CQqr/DQ0qpiwsiQAAAAAAAAAAAAAAAAAAAAAAMjJJABQUHkdAQF7dLCxe3QcHHkYAAAAWQkJi566u\r\n        //+pqfn/j4/T/w4OFf8PDxf/nZ3n/66u//+trf//p6f2/2dnmP8AAAD/PDxZ/7Gx//+Njf//Hh5i5wAA\r\n        ABYUFB5GQEBe3SwsXt0HBx5HGBhJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAABYAAAAWAAAAAQAA\r\n        ABhCQmLnsbH//3Z2rv8MDBL/AQEB/xAQGP+dnef/rq7//66u//9GRmf/BgYJ/wAAAP88PFn/sbH//42N\r\n        //8eHmLnAAAAGAAAAAEAAAAWAAAAFgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n        AAAAAAAAAAAAGENDYuewsP//mZnh/zAwR/8NDRP/VVV9/6qq+v+trf//rq7//3x8t/8XFyL/Fxci/319\r\n        uP+wsP//j4///x4eYucAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n        AAAAAAAAAAAAAAAAAAAAAAAYNjZi56Oj//+wsP//pKTv/52d5/+qqvf/rq7//66u//+urv//rq79/5+f\r\n        6v+fn+n/r6/9/6ys//98fP//Hx9i5wAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAA\r\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAURETt/VVW49pub7v+oqP//pKT3/6Oj9/+np///oaHy/6Wl\r\n        /P+lpfz/oqLy/6am/P+iovL/e3vv/z09uvgSEjqCAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n        AAAYGE+xAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYcHFnPMjKE/z8/qf8tLXP/LS1z/z8/\r\n        qf8kJFb/ODiV/zg4lf8kJFf/ODiV/yUlV/8fH1nTBQUTpAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n        AAAAAAAAAAAAAG9v3f8iImK5AAAAGwgIIQAAAAAAAAAAAAgIIQAAAAAbISFhuHV14/9YWKD4AAAAhgAA\r\n        AJEAAACTAAAATwAAALQAAABrAAAAawAAALUAAABrAAAAtgAAACUAAABiAAAAMgAAAAAAAAAAAAAAAAAA\r\n        AAAAAAAAAAAAAAAAAAAAAAAAra3//3d34P8hIWG4AAAATwAAAAgAAAAAAAAAHSEhYbl4eOL/enq0/x0d\r\n        Jq4AAAABAAAAOgAAAEMAAABHAAAAeQAAABAAAAATAAAAXAAAABMAAABdAAAAAAAAAAsAAAALAAAAAAAA\r\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjo+3/ra3//4eH4P9LS5r6EBA7fQAAACEhIWG4d3fj/4uL\r\n        z/9AQJD/Jydy4RISKJgDAw8jEhI7fDs7nvwgIFO4AAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAENDYsiYmOD/sbH//6am//9WVrv3JCRt6Xd3\r\n        4f2trf//p6f1/5eX5f9ubqv/Tk6b/QsLLOlYWL73jY3i/zIyUbgAAAAOAAAAAAAAAAAAAAAAAAAAAAAA\r\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJENDY8iiou//r6///6Ki\r\n        //+Njf//q6v//62t//+trf//rq7//6+v//+Bgbz/dHS9/5eX8P9FRWPJAAAAIAAAAAAAAAAAAAAAAAAA\r\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAA\r\n        AAMAAAABAAAAAQAAAAMAYAQfHwAAH/4AAD/8AAA//AAAP/gAAB/4AAAf+AAAH/gAAB/4AAAf4AAAB+AA\r\n        AAfgAAAH4AAAB+AAAAfgAAAH4AAAB/4AAH/+AAB/fgAAfz8AAP8eAAD/BAAE/wAAf/8AAH//AAD//ygA\r\n        AAAwAAAAYAAAAAEAIAAAAAAAACQAABILAAASCwAAAAAAAAAAAAAAra3/AK2t/wCtrf8Ara3/AK2t/wCt\r\n        rf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wAA\r\n        AP8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AAAA/1JS//+trf//ra3//62t\r\n        //+trf//ra3//62t//+trf//UlL//wAAAP8Ara3/AK2t/wAAAP//AAAA/wAAAP8AAAAA////AK2t/wD/\r\n        //8A////AP///wD///8Ara3/AP///wD///8A////AP///wCtrf8A////AP///wD///8A////AK2t/wD/\r\n        //8A////AP///wD///8AAAD/AP///wD///8A////AP///wCtrf8A////AP///wD///8AAAD/UlL//62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//9SUv//AAAA/wCtrf8A////AK2t/wAAAP//AAAA/wAAAP8A\r\n        AAAA////AK2t/wD///8A////AP///wD///8Ara3/AP///wD///8A////AP///wCtrf8A////AP///wD/\r\n        //8A////AK2t/wD///8A////AP///wD///8Ara3/AAAA/wD///8A////AP///wCtrf8A////AP///wD/\r\n        //8AAAD/ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//UlL//wAAAP8Ara3/AAAA//8A\r\n        AAD/AAAA/wAAAP8AAAAA////AK2t/wD///8A////AP///wD///8Ara3/AP///wD///8A////AP///wCt\r\n        rf8A////AP///wD///8A////AK2t/wD///8A////AP///wD///8Ara3/AP///wAAAP8A////AP///wCt\r\n        rf8A////AP///wAAAP9SUv//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//1JS\r\n        //8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAAA////AK2t/wD///8A////AP///wD///8Ara3/AP///wD/\r\n        //8A////AP///wCtrf8A////AP///wD///8A////AK2t/wD///8A////AP///wD///8Ara3/AP///wAA\r\n        AP8AAAD/AP///wCtrf8A////AP///wAAAP+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//ra3//62t//9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAAAra3/AK2t/wCtrf8Ara3/AK2t/wCt\r\n        rf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wAAAP8Ara3/AK2t/wCt\r\n        rf8Ara3/AAAA/1JS//9SUv//AAAA/wCtrf8Ara3/AAAA/1JS//+trf//ra3//wAAAP9SUv//ra3//62t\r\n        //+trf//ra3//62t//+trf//AAAA/62t//+trf//UlL//wAAAP//AAAA/wAAAP8AAAAA////AK2t/wD/\r\n        //8A////AP///wD///8Ara3/AP///wD///8A////AP///wCtrf8A////AAAA/wD///8Ara3/AAAA/wAA\r\n        AP8A////AP///wD///8AAAD/UlL//62t//+trf//UlL//wAAAP8A////AAAA/62t//+trf//AAAA/1JS\r\n        //+trf//AAAA/62t//+trf//ra3//62t//+trf//AAAA/wAAAP+trf//ra3//wAAAP//AAAA/wAAAP8A\r\n        AAAA////AK2t/wD///8A////AP///wD///8Ara3/AP///wAAAP8AAAD/AP///wCtrf8Ara3/AAAA/wAA\r\n        AP8AAAD//wAAAAAAAP8A////AP///wAAAP8AAAD/ra3//62t//+trf//ra3//wAAAP8AAAD/UlL//62t\r\n        //8AAAD//wAAAAAAAP8AAAD/UlL//62t//8AAAD/UlL//62t//8AAAD//wAAAP8AAAAAAAD/AAAA//8A\r\n        AAD/AAAA/wAAAP8AAAAA////AK2t/wD///8A////AP///wD///8AAAD/AAAA//8AAAD/AAAAAAAA/wAA\r\n        AP8AAAD//wAAAP8AAAD/AAAA/wAAAAAAAP8A////AAAA//8AAAAAAAD/ra3//62t//+trf//ra3//wAA\r\n        AP8AAAD/ra3//62t//8AAAD//wAAAP8AAAD/AAAAAAAA/wAAAP8AAAD/UlL//62t//8AAAD//wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAA////AK2t/wD///8AAAD/AAAA/wAAAP//AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD//wAAAP8AAAAAAAD/UlL//1JS\r\n        //9SUv//UlL//wAAAP9SUv//ra3//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP9SUv//ra3//wAA\r\n        AP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/AAAA/wAAAP//AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP8AAAD/AAAA/wAA\r\n        AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+trf//ra3//wAAAP8AAAD//wAAAP8AAAD/AAAA/wAAAAAA\r\n        AP+trf//ra3//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/AAAA/1JS\r\n        //9SUv//UlL//1JS//9SUv//UlL//1JS//9SUv//UlL//1JS//8AAAD/AAAA/1JS//9SUv//AAAA/wAA\r\n        AP8AAAD/AAAA/1JS//+trf//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/wAA\r\n        AP9SUv//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//UlL//1JS//9SUv//AAAA/62t//+trf//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAAAAAD/UlL//62t//+trf//ra3//62t//+trf//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA\r\n        AP8AAAD/ra3//62t//+trf//ra3//62t//+trf//UlL//wAAAP8AAAD//wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAAAAAP9SUv//ra3//62t//+trf//ra3//wAAAP8AAAD/AAAA////////////////////\r\n        /////////////wAAAP8AAK3/AAAA/wAAAP8AAAD/ra3//62t//+trf//ra3//1JS//8AAAD//wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAAAAAA/1JS//+trf//ra3//62t//8AAAD/AAAA/wAArf8AAP//AAAA////\r\n        /////////////////////////////wAAAP8AAP//AAD//wAA//8AAK3/AAAA/wAAAP+trf//ra3//62t\r\n        //9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//wAAAP8AAK3/AAD//wAA\r\n        //8AAP//AAAA/////////////////////////////////wAAAP8AAP//AAD//wAA//8AAP//AAD//wAA\r\n        rf8AAAD/ra3//62t//9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//+trf//ra3//wAA\r\n        AP8AAP//AAD//wAA//8AAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAP//AAD//wAA\r\n        //8AAP//AAD//wAA//8AAAD/ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t\r\n        //+trf//AAAA/wAArf8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA\r\n        //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAK3/AAAA/62t//+trf//UlL//wAAAP//AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAAAAAD/ra3//62t//+trf//AAAA/wAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAP//AAD//wAA\r\n        //8AAP//AAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAP//AAAA/62t//+trf//UlL//wAA\r\n        AP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//+trf//AAAA/wAA//8AAAD//////////////////////wAA\r\n        AP8AAP//AAD//wAA//8AAP//AAD//wAAAP///////////////////////////wAAAP8AAP//AAAA/62t\r\n        //+trf//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//+trf//ra3//wAAAP8AAAD/////////\r\n        /////////////wAAAP8AAK3/AACt/wAArf8AAK3/AACt/wAAAP///////////////////////////wAA\r\n        AP8AAAD/ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//+trf//ra3//62t\r\n        //8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA\r\n        AP8AAAD/AAAA/wAAAP+trf//ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//9SUv//AAAA//8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAAAAAA/1JS//+trf//ra3//62t//8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBS\r\n        rf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/62t//+trf//ra3//62t//9SUv//AAAA//8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAAAAAD/AAAA/wAAAP//AAAA/wAAAAAAAP9SUv//ra3//62t//+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//1JS\r\n        //8AAAD//wAAAP8AAAAAAAD/AAAA/wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAAAAAP+trf//ra3//1JS//8AAAD//wAAAP8AAAAAAAD/ra3//62t//8AUq3/AFKt/wBS\r\n        rf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/62t\r\n        //+trf//UlL//wAAAP//AAAA/wAAAAAAAP+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP+trf//AAAA/1JS//8AAAD/AAAA/wAAAP8AAAD/ra3//62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//ra3//62t//+trf//UlL//wAAAP8AAAD/AAAA/wAAAP+trf//AAAA/1JS//8AAAD//wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP+trf//AAAA/1JS//8AAAD/ra3//1JS\r\n        //8AAAD/ra3//62t//+trf//ra3//wAAAP8AAAD/AAAA/62t//+trf//ra3//62t//+trf//ra3//62t\r\n        //8AAAD/AAAA/wAAAP+trf//ra3//62t//+trf//UlL//wAAAP9SUv//ra3//wAAAP+trf//AAAA/1JS\r\n        //8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP+trf//AAAA/1JS\r\n        //8AAAD/ra3//1JS//8AAAD/ra3//62t//+trf//AAAA/wAAAP8AAAD/AAAA/wAAAP+trf//ra3//62t\r\n        //+trf//ra3//wAAAP8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//UlL//wAAAP9SUv//ra3//wAA\r\n        AP+trf//AAAA/1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAA\r\n        AP+trf//AAAA/1JS//8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//AAAA/wAAAP8AAAD/AAAA/wAA\r\n        AP+trf//ra3//62t//+trf//ra3//wAAAP8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//UlL//wAA\r\n        AP8AAAD/AAAA/wAAAP+trf//AAAA/1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAAAAAP+trf//ra3//1JS//8AAAD//wAAAP8AAAAAAAD/ra3//62t//+trf//ra3//62t\r\n        //8AAAD/AAAA/wAAAP+trf//ra3//62t//+trf//ra3//62t//+trf//AAAA/wAAAP8AAAD/ra3//62t\r\n        //+trf//UlL//wAAAP//AAAA/wAAAAAAAP+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/AAAA/wAAAP//AAAA/wAAAP8AAAAAAAD/ra3//62t\r\n        //+trf//ra3//62t//8AAAD/AAAA/wAAAP+trf//ra3//62t//+trf//ra3//62t//+trf//AAAA/wAA\r\n        AP8AAAD/ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8AAAAAAAD/AAAA/wAAAP//AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAAAAAD/ra3//62t//+trf//AAAA/wAAAP8AAAD/AAAA/wAAAP+trf//ra3//62t//+trf//ra3//wAA\r\n        AP8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//+trf//AAAA/wAAAP8AAAD/AAAA/wAAAP+trf//ra3//62t\r\n        //+trf//ra3//wAAAP8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//+trf//ra3//wAAAP8AAAD/AAAA/62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//8AAAD/AAAA/wAAAP+trf//ra3//62t//+trf//UlL//wAA\r\n        AP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//+trf//ra3//62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/UlL//62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//ra3//62t//9SUv//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAAAAAA/1JS//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//ra3//62t//+trf//ra3//1JS//9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP8AAAD/UlL//1JS//9SUv//AAAA/1JS//9SUv//UlL//wAA\r\n        AP9SUv//UlL//1JS//8AAAD/UlL//1JS//8AAAD/UlL//wAAAP8AAAD//wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAABSUv//AAAA//8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/1JS//9SUv//AAAA/wAAAP8AAAD/AAAA/wAA\r\n        AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA//8AAAAAAAD//wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAACtrf//UlL//wAA\r\n        AP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/UlL//62t//+trf//AAAA//8A\r\n        AAD/AAAAAAAA//8AAAD/AAAA/wAAAAAAAP//AAAA/wAAAP8AAAAAAAD//wAAAP8AAAAAAAD//wAAAP8A\r\n        AAD/AAAAAAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AACtrf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP9SUv//ra3//62t\r\n        //8AAAD//wAAAP8AAAD/AAAAAAAA//8AAAD/AAAA/wAAAAAAAP//AAAA/wAAAP8AAAAAAAD//wAAAP8A\r\n        AAAAAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAACtrf//ra3//62t//9SUv//AAAA/wAAAP//AAAA/wAAAP8AAAD/AAAAAAAA/1JS\r\n        //+trf//ra3//wAAAP8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/AAAA//8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAACtrf//ra3//62t//+trf//ra3//1JS//8AAAD//wAAAP8A\r\n        AAAAAAD/UlL//62t//+trf//AAAA/1JS//9SUv//AAAA/wAAAP//AAAA/wAAAAAAAP9SUv//UlL//wAA\r\n        AP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//+trf//ra3//62t\r\n        //9SUv//AAAA/wAAAP9SUv//ra3//62t//+trf//ra3//62t//8AAAD/ra3//1JS//8AAAD/AAAA/1JS\r\n        //+trf//ra3//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t\r\n        //+trf//ra3//62t//+trf//UlL//1JS//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//wAA\r\n        AP8AAAD/UlL//62t//+trf//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAAAAAP+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAAAAAAAAAcAAAAAAAAABwAAAAAAAAAPAAAAAAAAAB8AAAAAAAAADwAAAAAAAAAH\r\n        AAAAAAAAAAcAAAAAgAEAzwAAAMeIAcD/AAAD/9gD4f8AAB//gAHh/wAA//4AAAP/AAD/+AAAA/8AAP/w\r\n        AAAH/wAA/+AAAAf/AAD/wAAAA/8AAP/AAAAD/wAA/4AAAAH/AAD/gAAAAf8AAP+AAAAB/wAA/4AAAAH/\r\n        AAD/gAAAAf8AAP+AAAAB/wAA/8AAAAP/AAD/wAAAA/8AAPxgAAAGPwAA+DAAAAwfAAD4AAAAAB8AAPgA\r\n        AAAAHwAA+AAAAAAfAAD4AAAAAB8AAPgwAAAMHwAA/HAAAA4/AAD/8AAAD/8AAP/wAAAP/wAA//AAAA//\r\n        AAD/8AAAD/8AAP/wAAAP/wAA//gAAB//AAB//AAAP/8AAD/4AAC//wAAH/Bu7d//AAAP4O7t//8AAAPA\r\n        +f///wAAAYAw////AAAAAAD///8AAIAAAf///wAAwAAD////AAAoAAAAMAAAAGAAAAABACAAAAAAAAAk\r\n        AAASCwAAEgsAAAAAAAAAAAAAAK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCt\r\n        rf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8AAAD/AK2t/wCtrf8Ara3/AK2t/wCt\r\n        rf8Ara3/AK2t/wCtrf8Ara3/AK2t/wAAAP9SUv//ra3//62t//+trf//ra3//62t//+trf//ra3//1JS\r\n        //8AAAD/AK2t/wCtrf8AAAD//wAAAP8AAAD/AAAAAP///wCtrf8A////AP///wD///8A////AK2t/wD/\r\n        //8A////AP///wD///8Ara3/AP///wD///8A////AP///wCtrf8A////AP///wD///8A////AAAA/wD/\r\n        //8A////AP///wD///8Ara3/AP///wD///8A////AAAA/1JS//+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//UlL//wAAAP8Ara3/AP///wCtrf8AAAD//wAAAP8AAAD/AAAAAP///wCtrf8A////AP///wD/\r\n        //8A////AK2t/wD///8A////AP///wD///8Ara3/AP///wD///8A////AP///wCtrf8A////AP///wD/\r\n        //8A////AK2t/wAAAP8A////AP///wD///8Ara3/AP///wD///8A////AAAA/62t//+trf//ra3//62t\r\n        //+trf//ra3//62t//+trf//ra3//1JS//8AAAD/AK2t/wAAAP//AAAA/wAAAP8AAAD/AAAAAP///wCt\r\n        rf8A////AP///wD///8A////AK2t/wD///8A////AP///wD///8Ara3/AP///wD///8A////AP///wCt\r\n        rf8A////AP///wD///8A////AK2t/wD///8AAAD/AP///wD///8Ara3/AP///wD///8AAAD/UlL//62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//9SUv//AAAA//8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAAAP///wCtrf8A////AP///wD///8A////AK2t/wD///8A////AP///wD///8Ara3/AP///wD/\r\n        //8A////AP///wCtrf8A////AP///wD///8A////AK2t/wD///8AAAD/AAAA/wD///8Ara3/AP///wD/\r\n        //8AAAD/ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//UlL//wAA\r\n        AP//AAAA/wAAAP8AAAD/AAAAAK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCt\r\n        rf8Ara3/AK2t/wCtrf8Ara3/AK2t/wCtrf8AAAD/AK2t/wCtrf8Ara3/AK2t/wAAAP9SUv//UlL//wAA\r\n        AP8Ara3/AK2t/wAAAP9SUv//ra3//62t//8AAAD/UlL//62t//+trf//ra3//62t//+trf//ra3//wAA\r\n        AP+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAAAP///wCtrf8A////AP///wD///8A////AK2t/wD/\r\n        //8A////AP///wD///8Ara3/AP///wAAAP8A////AK2t/wAAAP8AAAD/AP///wD///8A////AAAA/1JS\r\n        //+trf//ra3//1JS//8AAAD/AP///wAAAP+trf//ra3//wAAAP9SUv//ra3//wAAAP+trf//ra3//62t\r\n        //+trf//ra3//wAAAP8AAAD/ra3//62t//8AAAD//wAAAP8AAAD/AAAAAP///wCtrf8A////AP///wD/\r\n        //8A////AK2t/wD///8AAAD/AAAA/wD///8Ara3/AK2t/wAAAP8AAAD/AAAA//8AAAAAAAD/AP///wD/\r\n        //8AAAD/AAAA/62t//+trf//ra3//62t//8AAAD/AAAA/1JS//+trf//AAAA//8AAAAAAAD/AAAA/1JS\r\n        //+trf//AAAA/1JS//+trf//AAAA//8AAAD/AAAAAAAA/wAAAP//AAAA/wAAAP8AAAD/AAAAAP///wCt\r\n        rf8A////AP///wD///8A////AAAA/wAAAP//AAAA/wAAAAAAAP8AAAD/AAAA//8AAAD/AAAA/wAAAP8A\r\n        AAAAAAD/AP///wAAAP//AAAAAAAA/62t//+trf//ra3//62t//8AAAD/AAAA/62t//+trf//AAAA//8A\r\n        AAD/AAAA/wAAAAAAAP8AAAD/AAAA/1JS//+trf//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAAAP///wCtrf8A////AAAA/wAAAP8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAAAAAA//8AAAD/AAAAAAAA/1JS//9SUv//UlL//1JS//8AAAD/UlL//62t\r\n        //8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/UlL//62t//8AAAD//wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAAAAAA/wAAAP8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA\r\n        AP8AAAD/ra3//62t//8AAAD/AAAA//8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t//8AAAD//wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/wAAAP9SUv//UlL//1JS//9SUv//UlL//1JS\r\n        //9SUv//UlL//1JS//9SUv//AAAA/wAAAP9SUv//UlL//wAAAP8AAAD/AAAA/wAAAP9SUv//ra3//wAA\r\n        AP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP8AAAD/UlL//62t//+trf//ra3//62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//1JS//9SUv//UlL//wAA\r\n        AP+trf//ra3//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/1JS//+trf//ra3//62t\r\n        //+trf//ra3//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/62t//+trf//ra3//62t\r\n        //+trf//ra3//1JS//8AAAD/AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/UlL//62t\r\n        //+trf//ra3//62t//8AAAD/AAAA/wAAAP////////////////////////////////8AAAD/AACt/wAA\r\n        AP8AAAD/AAAA/62t//+trf//ra3//62t//9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAA\r\n        AP9SUv//ra3//62t//+trf//AAAA/wAAAP8AAK3/AAD//wAAAP//////////////////////////////\r\n        //8AAAD/AAD//wAA//8AAP//AACt/wAAAP8AAAD/ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAAAAAP+trf//ra3//62t//8AAAD/AACt/wAA//8AAP//AAD//wAAAP//////////////\r\n        //////////////////8AAAD/AAD//wAA//8AAP//AAD//wAA//8AAK3/AAAA/62t//+trf//UlL//wAA\r\n        AP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//62t//8AAAD/AAD//wAA//8AAP//AAD//wAA\r\n        AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAD//wAA//8AAP//AAD//wAA//8AAP//AAAA/62t\r\n        //+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//wAAAP8AAK3/AAD//wAA\r\n        //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA\r\n        //8AAP//AACt/wAAAP+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//wAA\r\n        AP8AAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAD//wAA//8AAP//AAD//wAA//8AAAD/AAAA/wAA\r\n        AP8AAAD/AAAA/wAAAP8AAAD/AAD//wAAAP+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t\r\n        //+trf//ra3//wAAAP8AAP//AAAA//////////////////////8AAAD/AAD//wAA//8AAP//AAD//wAA\r\n        //8AAAD///////////////////////////8AAAD/AAD//wAAAP+trf//ra3//1JS//8AAAD//wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAAAAAA/62t//+trf//ra3//62t//8AAAD/AAAA//////////////////////8AAAD/AACt/wAA\r\n        rf8AAK3/AACt/wAArf8AAAD///////////////////////////8AAAD/AAAA/62t//+trf//ra3//1JS\r\n        //8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//62t//+trf//AAAA/wAAAP8AAAD/AAAA/wAA\r\n        AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/ra3//62t\r\n        //+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP9SUv//ra3//62t\r\n        //+trf//AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBS\r\n        rf8AUq3/AFKt/wBSrf+trf//ra3//62t//+trf//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/wAAAP8AAAD//wAAAP8A\r\n        AAAAAAD/UlL//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//9SUv//AAAA//8AAAD/AAAAAAAA/wAA\r\n        AP8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t\r\n        //9SUv//AAAA//8AAAD/AAAAAAAA/62t//+trf//AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBS\r\n        rf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf8AUq3/AFKt/wBSrf+trf//ra3//1JS//8AAAD//wAAAP8A\r\n        AAAAAAD/ra3//62t//9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAAAAAD/ra3//wAAAP9SUv//AAAA/wAAAP8AAAD/AAAA/62t//+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//1JS\r\n        //8AAAD/AAAA/wAAAP8AAAD/ra3//wAAAP9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAAAAAD/ra3//wAAAP9SUv//AAAA/62t//9SUv//AAAA/62t//+trf//ra3//62t\r\n        //8AAAD/AAAA/wAAAP+trf//ra3//62t//+trf//ra3//62t//+trf//AAAA/wAAAP8AAAD/ra3//62t\r\n        //+trf//ra3//1JS//8AAAD/UlL//62t//8AAAD/ra3//wAAAP9SUv//AAAA//8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//wAAAP9SUv//AAAA/62t//9SUv//AAAA/62t\r\n        //+trf//ra3//wAAAP8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//ra3//62t//8AAAD/AAAA/wAA\r\n        AP8AAAD/AAAA/62t//+trf//ra3//1JS//8AAAD/UlL//62t//8AAAD/ra3//wAAAP9SUv//AAAA//8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//wAAAP9SUv//AAAA/wAA\r\n        AP8AAAD/AAAA/62t//+trf//ra3//wAAAP8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//ra3//62t\r\n        //8AAAD/AAAA/wAAAP8AAAD/AAAA/62t//+trf//ra3//1JS//8AAAD/AAAA/wAAAP8AAAD/ra3//wAA\r\n        AP9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t\r\n        //9SUv//AAAA//8AAAD/AAAAAAAA/62t//+trf//ra3//62t//+trf//AAAA/wAAAP8AAAD/ra3//62t\r\n        //+trf//ra3//62t//+trf//ra3//wAAAP8AAAD/AAAA/62t//+trf//ra3//1JS//8AAAD//wAAAP8A\r\n        AAAAAAD/ra3//62t//9SUv//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAAAAAA/wAAAP8AAAD//wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//62t//+trf//AAAA/wAA\r\n        AP8AAAD/ra3//62t//+trf//ra3//62t//+trf//ra3//wAAAP8AAAD/AAAA/62t//+trf//ra3//1JS\r\n        //8AAAD//wAAAP8AAAD/AAAAAAAA/wAAAP8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//wAA\r\n        AP8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//ra3//62t//8AAAD/AAAA/wAAAP8AAAD/AAAA/62t\r\n        //+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t\r\n        //+trf//ra3//wAAAP8AAAD/AAAA/wAAAP8AAAD/ra3//62t//+trf//ra3//62t//8AAAD/AAAA/wAA\r\n        AP8AAAD/AAAA/62t//+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAAAAAA/62t//+trf//ra3//62t//8AAAD/AAAA/wAAAP+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//AAAA/wAAAP8AAAD/ra3//62t//+trf//ra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//1JS//8AAAD//wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAA/1JS//+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//UlL//1JS\r\n        //8AAAD//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP9SUv//ra3//62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t\r\n        //9SUv//UlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAAAAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAAAAAD/AAAA/1JS//9SUv//UlL//wAAAP9SUv//UlL//1JS//8AAAD/UlL//1JS//9SUv//AAAA/1JS\r\n        //9SUv//AAAA/1JS//8AAAD/AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAAUlL//wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAAAAAP9SUv//UlL//wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA\r\n        AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP//AAAAAAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAra3//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAAAAAA/1JS//+trf//ra3//wAAAP//AAAA/wAAAAAAAP//AAAA/wAAAP8A\r\n        AAAAAAD//wAAAP8AAAD/AAAAAAAA//8AAAD/AAAAAAAA//8AAAD/AAAA/wAAAAAAAP//AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAra3//62t//9SUv//AAAA//8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/UlL//62t//+trf//AAAA//8AAAD/AAAA/wAAAAAA\r\n        AP//AAAA/wAAAP8AAAAAAAD//wAAAP8AAAD/AAAAAAAA//8AAAD/AAAAAAAA//8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAra3//62t\r\n        //+trf//UlL//wAAAP8AAAD//wAAAP8AAAD/AAAA/wAAAAAAAP9SUv//ra3//62t//8AAAD/AAAA//8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAAAAAA/wAAAP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAAra3//62t//+trf//ra3//62t//9SUv//AAAA//8AAAD/AAAAAAAA/1JS//+trf//ra3//wAA\r\n        AP9SUv//UlL//wAAAP8AAAD//wAAAP8AAAAAAAD/UlL//1JS//8AAAD//wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAAAAAA/62t//+trf//ra3//62t//+trf//UlL//wAAAP8AAAD/UlL//62t\r\n        //+trf//ra3//62t//+trf//AAAA/62t//9SUv//AAAA/wAAAP9SUv//ra3//62t//8AAAD//wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAP+trf//ra3//62t//+trf//ra3//1JS\r\n        //9SUv//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//8AAAD/AAAA/1JS//+trf//ra3//wAA\r\n        AP//AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAD/ra3//62t\r\n        //+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t//+trf//ra3//62t\r\n        //+trf//AAAA//8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8A\r\n        AAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAH\r\n        AAAAAAAAAAcAAAAAAAAADwAAAAAAAAAfAAAAAAAAAA8AAAAAAAAABwAAAAAAAAAHAAAAAIABAM8AAADH\r\n        iAHA/wAAA//YA+H/AAAf/4AB4f8AAP/+AAAD/wAA//gAAAP/AAD/8AAAB/8AAP/gAAAH/wAA/8AAAAP/\r\n        AAD/wAAAA/8AAP+AAAAB/wAA/4AAAAH/AAD/gAAAAf8AAP+AAAAB/wAA/4AAAAH/AAD/gAAAAf8AAP/A\r\n        AAAD/wAA/8AAAAP/AAD8YAAABj8AAPgwAAAMHwAA+AAAAAAfAAD4AAAAAB8AAPgAAAAAHwAA+AAAAAAf\r\n        AAD4MAAADB8AAPxwAAAOPwAA//AAAA//AAD/8AAAD/8AAP/wAAAP/wAA//AAAA//AAD/8AAAD/8AAP/4\r\n        AAAf/wAAf/wAAD//AAA/+AAAv/8AAB/wbu3f/wAAD+Du7f//AAADwPn///8AAAGAMP///wAAAAAA////\r\n        AACAAAH///8AAMAAA////wAA\r\n</value>\r\n  </data>\r\n</root>"
  },
  {
    "path": "LibSidWiz/ProcessWrapper.cs",
    "content": "﻿using System;\r\nusing System.Collections.Concurrent;\r\nusing System.Collections.Generic;\r\nusing System.Diagnostics;\r\nusing System.Threading;\r\n\r\nnamespace LibSidWiz\r\n{\r\n    public class ProcessWrapper: IDisposable\r\n    {\r\n        private readonly Process _process;\r\n        private readonly BlockingCollection<string> _lines = new(new ConcurrentQueue<string>());\r\n        private readonly CancellationTokenSource _cancellationTokenSource;\r\n        private int _streamCount;\r\n\r\n        public ProcessWrapper(string filename, string arguments, bool captureStdErr = false, bool captureStdOut = true, bool showConsole = false)\r\n        {\r\n            _cancellationTokenSource = new CancellationTokenSource();\r\n\r\n            _process = new Process\r\n            {\r\n                StartInfo = new ProcessStartInfo\r\n                {\r\n                    FileName = filename,\r\n                    Arguments = arguments,\r\n                    RedirectStandardOutput = captureStdOut,\r\n                    RedirectStandardError = captureStdErr,\r\n                    UseShellExecute = false,\r\n                    CreateNoWindow = !showConsole\r\n                },\r\n                EnableRaisingEvents = true\r\n            };\r\n            if (_process == null)\r\n            {\r\n                throw new Exception($\"Error running {filename} {arguments}\");\r\n            }\r\n\r\n            if (captureStdOut)\r\n            {\r\n                _process.OutputDataReceived += OnText;\r\n            }\r\n            if (captureStdErr)\r\n            {\r\n                _process.ErrorDataReceived += OnText;\r\n            }\r\n\r\n            _process.Start();\r\n\r\n            if (captureStdOut)\r\n            {\r\n                _process.BeginOutputReadLine();\r\n                ++_streamCount;\r\n            }\r\n            if (captureStdErr)\r\n            {\r\n                _process.BeginErrorReadLine();\r\n                ++_streamCount;\r\n            }\r\n        }\r\n\r\n        private void OnText(object sender, DataReceivedEventArgs e)\r\n        {\r\n            if (_cancellationTokenSource.IsCancellationRequested)\r\n            {\r\n                return;\r\n            }\r\n            try\r\n            {\r\n                _lines.TryAdd(e.Data, -1, _cancellationTokenSource.Token);\r\n            }\r\n            catch (OperationCanceledException)\r\n            {\r\n                // Discard it\r\n            }\r\n        }\r\n\r\n        /// <summary>\r\n        /// Blocks while waiting for the next line...\r\n        /// </summary>\r\n        public IEnumerable<string> Lines()\r\n        {\r\n            while (!_cancellationTokenSource.IsCancellationRequested)\r\n            {\r\n                string line;\r\n                try\r\n                {\r\n                    // Blocking take\r\n                    line = _lines.Take(_cancellationTokenSource.Token);\r\n                }\r\n                catch (OperationCanceledException)\r\n                {\r\n                    yield break;\r\n                }\r\n\r\n                if (line == null)\r\n                {\r\n                    if (--_streamCount == 0)\r\n                    {\r\n                        // We see a null to indicate the end of each stream. We break on the last one.\r\n                        yield break;\r\n                    }\r\n                    // Else drop it\r\n                    continue;\r\n                }\r\n\r\n                yield return line;\r\n            }\r\n        }\r\n\r\n        public void WaitForExit() => _process.WaitForExit();\r\n\r\n        public void Dispose()\r\n        {\r\n            _cancellationTokenSource.Cancel();\r\n            _lines.CompleteAdding();\r\n            if (_process != null)\r\n            {\r\n                // Try to kill the process if it is alive\r\n                if (!_process.HasExited)\r\n                {\r\n                    try\r\n                    {\r\n                        _process.EnableRaisingEvents = false;\r\n                        _process.Kill();\r\n                    }\r\n                    catch (Exception)\r\n                    {\r\n                        // May throw if the process terminates first\r\n                    }\r\n                }\r\n\r\n                _process.Dispose();\r\n            }\r\n\r\n            _cancellationTokenSource.Dispose();\r\n            _lines.Dispose();\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "LibSidWiz/SampleBuffer.cs",
    "content": "﻿using System;\r\nusing NAudio.Dsp;\r\nusing NAudio.Wave;\r\n\r\nnamespace LibSidWiz\r\n{\r\n    internal class SampleBuffer: IDisposable\r\n    {\r\n        private readonly WaveStream _reader;\r\n        private readonly ISampleProvider _sampleProvider;\r\n\r\n        private class Chunk\r\n        {\r\n            public long Offset;\r\n            public long End;\r\n            public float[] Buffer;\r\n\r\n            public bool TryGet(long index, out float value)\r\n            {\r\n                if (index >= Offset && index < End)\r\n                {\r\n                    value = Buffer[index - Offset];\r\n                    return true;\r\n                }\r\n\r\n                value = 0;\r\n                return false;\r\n            }\r\n        }\r\n\r\n        private readonly Chunk _chunk1;\r\n        private readonly Chunk _chunk2;\r\n\r\n        // 4 bytes per sample so this is 1MB\r\n        // If we are rendering ~16 frames at once, we need (typically) 1323 samples per frame,\r\n        // which is a window of 84KB. This is far from causing us trouble here.\r\n        private const int ChunkSize = 256 * 1024 * 1;\r\n\r\n        public long Count { get; }\r\n\r\n        public int SampleRate { get; }\r\n\r\n        public TimeSpan Length { get; }\r\n\r\n        public float Max { get; private set; }\r\n\r\n        public float Min { get; private set; }\r\n\r\n        public SampleBuffer(string filename, Channel.Sides side, bool filter)\r\n        {\r\n            _reader = new AudioFileReader(filename);\r\n            Count = _reader.Length * 8 / _reader.WaveFormat.BitsPerSample / _reader.WaveFormat.Channels;\r\n            SampleRate = _reader.WaveFormat.SampleRate;\r\n            Length = _reader.TotalTime;\r\n            _sampleProvider = side switch\r\n            {\r\n                Channel.Sides.Left => _reader.ToSampleProvider().ToMono(1.0f, 0.0f),\r\n                Channel.Sides.Right => _reader.ToSampleProvider().ToMono(0.0f, 1.0f),\r\n                Channel.Sides.Mix => _reader.ToSampleProvider().ToMono(),\r\n                _ => throw new ArgumentOutOfRangeException(nameof(side), side, null)\r\n            };\r\n\r\n            if (filter)\r\n            {\r\n                _sampleProvider = new HighPassSampleProvider(_sampleProvider);\r\n            }\r\n\r\n            _chunk1 = new Chunk\r\n            {\r\n                Buffer = new float[ChunkSize],\r\n                Offset = -1,\r\n                End = -1\r\n            };\r\n            _chunk2 = new Chunk\r\n            {\r\n                Buffer = new float[ChunkSize],\r\n                Offset = -1,\r\n                End = -1\r\n            };\r\n        }\r\n\r\n        public void Dispose()\r\n        {\r\n            _reader.Dispose();\r\n        }\r\n\r\n        public float this[long index]\r\n        {\r\n            get\r\n            {\r\n                // We may be accessed from multiple threads; we therefore need to lock access to avoid concurrent access.\r\n                lock (this)\r\n                {\r\n                    if (_chunk1.TryGet(index, out var value) || _chunk2.TryGet(index, out value))\r\n                    {\r\n                        return value;\r\n                    }\r\n\r\n                    // If we are asked for sample 0, reset the buffers\r\n                    if (index == 0)\r\n                    {\r\n                        _chunk1.Offset = _chunk1.End = _chunk2.Offset = _chunk2.End = -1;\r\n                    }\r\n\r\n                    // Else pick the lower index chunk to read into\r\n                    var chunk = _chunk1.Offset < _chunk2.Offset ? _chunk1 : _chunk2;\r\n                    // Pick the rounded offset\r\n                    chunk.Offset = (index / ChunkSize) * ChunkSize;\r\n                    chunk.End = chunk.Offset + ChunkSize;\r\n                    _reader.Position = chunk.Offset * _reader.WaveFormat.BitsPerSample / 8 * _reader.WaveFormat.Channels;\r\n                    _sampleProvider.Read(chunk.Buffer, 0, ChunkSize);\r\n                    return chunk.Buffer[index - chunk.Offset];\r\n                }\r\n            }\r\n        }\r\n\r\n        public void Analyze()\r\n        {\r\n            Min = float.MaxValue;\r\n            Max = float.MinValue;\r\n            for (int i = 0; i < Count; ++i)\r\n            {\r\n                var sample = this[i];\r\n                Min = Math.Min(Min, sample);\r\n                Max = Math.Max(Max, sample);\r\n            }\r\n        }\r\n    }\r\n\r\n    internal class HighPassSampleProvider(ISampleProvider sampleProvider) : ISampleProvider\r\n    {\r\n        private readonly BiQuadFilter _filter = BiQuadFilter.HighPassFilter(sampleProvider.WaveFormat.SampleRate, 20, 1);\r\n\r\n        public int Read(float[] buffer, int offset, int count)\r\n        {\r\n            int result = sampleProvider.Read(buffer, offset, count);\r\n\r\n            // Apply the filter\r\n            for (int i = 0; i < result; ++i)\r\n            {\r\n                buffer[i] = _filter.Transform(buffer[i]);\r\n            }\r\n\r\n            return result;\r\n        }\r\n\r\n        public WaveFormat WaveFormat => sampleProvider.WaveFormat;\r\n    }\r\n}\r\n"
  },
  {
    "path": "LibSidWiz/Triggers/AutoCorrelationTrigger.cs",
    "content": "﻿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    // ReSharper disable once UnusedType.Global\r\n    internal class AutoCorrelationTrigger: ITriggerAlgorithm\r\n    {\r\n        private float[] _normalDistribution;\r\n\r\n        public int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex)\r\n        {\r\n            var width = endIndex - startIndex;\r\n            if (_normalDistribution == null || _normalDistribution.Length != width)\r\n            {\r\n                _normalDistribution = new float[width];\r\n                // Generate distribution\r\n                // We fit 2 standard deviations to the width \r\n                double variance = width * width / 16.0;\r\n                double mu = width / 2.0;\r\n                var scale = Math.Sqrt(2 * Math.PI * variance);\r\n                for (int i = 0; i < width; ++i)\r\n                {\r\n                    _normalDistribution[i] = (float) (Math.Exp(-(i - mu) * (i - mu) / (2 * variance)) / scale);\r\n                }\r\n            }\r\n\r\n            var maxCorrelation = double.MinValue;\r\n            var bestOffset = startIndex;\r\n            var previousStart = previousIndex - width / 2;\r\n            // We compute the correlation between the previous window and each possible offset in the new one,\r\n            // weighted by a normal distribution so we prefer ones near the middle.\r\n            // The correlation is defined as\r\n            // sum((x - mean(x)) * (y - mean(y)) / sqrt(sum(pow(x - mean(x), 2)) * sum(pow(y - mean(y), 2)))\r\n            // where x and y are the samples in each series. Since the reference is fixed, we can compute it once.\r\n            float sumY = 0;\r\n            float sumX = 0;\r\n            for (int i = 0; i < width; ++i)\r\n            {\r\n                sumY += channel.GetSample(previousStart + i);\r\n                sumX += channel.GetSample(startIndex + i);\r\n            }\r\n            var meanY = sumY / width;\r\n            float sumSquaredYDiff = 0;\r\n            for (int i = 0; i < width; ++i)\r\n            {\r\n                var y = channel.GetSample(previousStart + i);\r\n                var yDiff = y - meanY;\r\n                sumSquaredYDiff += yDiff * yDiff;\r\n            }\r\n\r\n            // ReSharper disable once CompareOfFloatsByEqualityOperator\r\n            if (sumSquaredYDiff == 0.0f)\r\n            {\r\n                // No point continuing - we return the middle of the data\r\n                return startIndex + width / 2;\r\n            }\r\n\r\n            var correlations = new double[width];\r\n            for (int trialOffset = 0; trialOffset < width; ++trialOffset)\r\n            {\r\n                // We compute mean(x) as we go\r\n                var meanX = sumX / width;\r\n                // ...by amending the moving sum. This will accumulate floating point error but it's not significant.\r\n                sumX -= channel.GetSample(startIndex + trialOffset);\r\n                sumX += channel.GetSample(startIndex + trialOffset + width);\r\n\r\n                // sum((x - mean(x)) * (y - mean(y)) / sqrt(sum(pow(x - mean(x), 2)) * sum(pow(y - mean(y), 2)))\r\n                float sumTop = 0;\r\n                float sumSquaredXDiff = 0;\r\n                for (int i = 0; i < width; ++i)\r\n                {\r\n                    var x = channel.GetSample(startIndex + trialOffset + i);\r\n                    var y = channel.GetSample(previousStart + i);\r\n                    sumTop += (x - meanX) * (y - meanY);\r\n                    sumSquaredXDiff += (x - meanX) * (x - meanX);\r\n                }\r\n\r\n                var correlation = sumTop / Math.Sqrt(sumSquaredXDiff * sumSquaredYDiff);\r\n\r\n                // We then weight by our normal distribution so we will prefer points near the middle.\r\n                correlation *= _normalDistribution[trialOffset];\r\n\r\n                // debug\r\n                correlations[trialOffset] = correlation;\r\n\r\n                if (correlation > maxCorrelation)\r\n                {\r\n                    maxCorrelation = correlation;\r\n                    bestOffset = trialOffset;\r\n                }\r\n            }\r\n\r\n#if DEBUG\r\n            Debug.WriteLine($\"Autocorrelation: between {startIndex} and {endIndex}, max = {maxCorrelation}, offset = {bestOffset} ({(float)(bestOffset - startIndex)/(endIndex - startIndex):P})\");\r\n\r\n            var sb = new StringBuilder();\r\n            for (int i = 0; i < width; ++i)\r\n            {\r\n                sb.AppendLine($\"{channel.GetSample(previousStart + i)}\\t{channel.GetSample(startIndex + i)}\\t{correlations[i]}\");\r\n            }\r\n#endif\r\n\r\n            return startIndex + bestOffset;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "LibSidWiz/Triggers/BiggestPositiveWaveAreaTrigger.cs",
    "content": "﻿using System;\r\n\r\nnamespace LibSidWiz.Triggers\r\n{\r\n    /// <summary>\r\n    /// Finds the wave with the biggest positive area (= sum of positive samples)\r\n    /// </summary>\r\n    // ReSharper disable once UnusedType.Global\r\n    internal class BiggestPositiveWaveAreaTrigger : ITriggerAlgorithm\r\n    {\r\n        public int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex)\r\n        {\r\n            int bestOffset = -1;\r\n            int lastCrossingPoint = endIndex;\r\n            float previousSample = channel.GetSample(startIndex);\r\n            float bestArea = 0;\r\n            float currentArea = float.MinValue;\r\n\r\n            // For each sample...\r\n            for (int i = startIndex + 1; i < endIndex; ++i)\r\n            {\r\n                // Add on the area\r\n                var sample = channel.GetSample(i);\r\n                currentArea += Math.Abs(sample);\r\n                if (sample > 0 && previousSample <= 0)\r\n                {\r\n                    // Positive edge - reset\r\n                    lastCrossingPoint = i;\r\n                    currentArea = sample;\r\n                }\r\n                else if (sample <= 0 && previousSample > 0)\r\n                {\r\n                    // Negative edge - check if it's a new biggest\r\n                    if (currentArea > bestArea)\r\n                    {\r\n                        bestArea = currentArea;\r\n                        bestOffset = lastCrossingPoint;\r\n                    }\r\n                }\r\n\r\n                previousSample = sample;\r\n            }\r\n            return bestOffset;\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "LibSidWiz/Triggers/BiggestWaveAreaTrigger.cs",
    "content": "﻿using System;\r\n\r\nnamespace LibSidWiz.Triggers\r\n{\r\n    /// <summary>\r\n    /// Finds the positive+negative wave with the biggest area (= sum of absolute samples)\r\n    /// </summary>\r\n    // ReSharper disable once UnusedType.Global\r\n    internal class BiggestWaveAreaTrigger : ITriggerAlgorithm\r\n    {\r\n        public int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex)\r\n        {\r\n            int bestOffset = -1;\r\n            int lastCrossingPoint = endIndex;\r\n            float previousSample = channel.GetSample(startIndex);\r\n            float bestArea = 0;\r\n            float currentArea = float.MinValue;\r\n\r\n            // For each sample...\r\n            for (int i = startIndex + 1; i < endIndex; ++i)\r\n            {\r\n                // Add on the area\r\n                var sample = channel.GetSample(i);\r\n                currentArea += Math.Abs(sample);\r\n                if (sample > 0 && previousSample <= 0)\r\n                {\r\n                    // Positive edge - check if it's a new biggest\r\n                    if (currentArea > bestArea)\r\n                    {\r\n                        bestArea = currentArea;\r\n                        bestOffset = lastCrossingPoint;\r\n                    }\r\n\r\n                    // And reset\r\n                    lastCrossingPoint = i;\r\n                    currentArea = sample;\r\n                }\r\n\r\n                previousSample = sample;\r\n            }\r\n            return bestOffset;\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "LibSidWiz/Triggers/ITriggerAlgorithm.cs",
    "content": "﻿namespace LibSidWiz.Triggers\r\n{\r\n    public interface ITriggerAlgorithm\r\n    {\r\n        /// <summary>\r\n        /// Finds a \"trigger point\" within a channel's samples\r\n        /// </summary>\r\n        /// <param name=\"channel\">Channel object holding samples</param>\r\n        /// <param name=\"startIndex\">Index of start of frame for analysis</param>\r\n        /// <param name=\"endIndex\">Index of end of frame for analysis</param>\r\n        /// <param name=\"previousIndex\">Index of previously found trigger</param>\r\n        /// <returns>Index of the trigger point, should be between startIndex and endIndex. Return -1 for failure.</returns>\r\n        int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex);\r\n    }\r\n}"
  },
  {
    "path": "LibSidWiz/Triggers/MiddleWidest.cs",
    "content": "﻿using System.Collections.Generic;\r\n\r\nnamespace LibSidWiz.Triggers\r\n{\r\n    /// <summary>\r\n    /// This corresponds to SidWiz's \"alternate\" algorithm.\r\n    /// We measure the width of each full wave, and then select the widest ones.\r\n    /// We then select the start point of the \"middle\" one, if more than one was found.\r\n    /// </summary>\r\n    // ReSharper disable once UnusedType.Global\r\n    class MiddleWidest: ITriggerAlgorithm\r\n    {\r\n        public int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex)\r\n        {\r\n            var candidates = new List<int>();\r\n            int lastCrossingPoint = endIndex;\r\n            float previousSample = channel.GetSample(startIndex);\r\n            int bestLength = 0;\r\n\r\n            // For each sample...\r\n            for (int i = startIndex + 1; i < endIndex; ++i)\r\n            {\r\n                var sample = channel.GetSample(i);\r\n                if (sample > 0 && previousSample <= 0)\r\n                {\r\n                    // Positive edge\r\n                    int length = i - lastCrossingPoint;\r\n                    if (length > bestLength)\r\n                    {\r\n                        candidates.Clear();\r\n                        bestLength = length;\r\n                    }\r\n\r\n                    if (length == bestLength)\r\n                    {\r\n                        candidates.Add(lastCrossingPoint);\r\n                    }\r\n\r\n                    lastCrossingPoint = i;\r\n                }\r\n\r\n                previousSample = sample;\r\n            }\r\n\r\n            if (candidates.Count == 0)\r\n            {\r\n                return -1;\r\n            }\r\n\r\n            // We select the \"middle\" one, preferring the one on the right if even\r\n            return candidates[candidates.Count / 2];\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "LibSidWiz/Triggers/NullTrigger.cs",
    "content": "﻿namespace LibSidWiz.Triggers\r\n{\r\n    /// <summary>\r\n    /// Null algorithm just returns the first sample it's given\r\n    /// </summary>\r\n    // ReSharper disable once UnusedType.Global\r\n    internal class NullTrigger: ITriggerAlgorithm\r\n    {\r\n        public int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex)\r\n        {\r\n            return startIndex;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "LibSidWiz/Triggers/PeakSpeedTrigger.cs",
    "content": "﻿namespace LibSidWiz.Triggers\r\n{\r\n    /// <summary>\r\n    /// Finds the positive edge which most quickly reaches the peak value in the sample range.\r\n    /// This is implemented in a slightly complicated way to make it do it with a single pass over the samples,\r\n    /// you could implement it as:\r\n    /// 1. Find first zero crossing\r\n    /// 2. Find max sample value after that\r\n    /// 4. Select the zero crossing closest to a following max value\r\n    /// This algorithm is based code from オップナー2608.\r\n    /// This algorithm can show good stability for waves which cross the zero point more than once.\r\n    /// </summary>\r\n    public class PeakSpeedTrigger : ITriggerAlgorithm\r\n    {\r\n        public int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex)\r\n        {\r\n            float peakValue = float.MinValue;\r\n            int shortestDistance = int.MaxValue;\r\n            int result = -1;\r\n            int i = startIndex;\r\n            while (i < endIndex)\r\n            {\r\n                // First find a positive edge crossing zero\r\n                while (channel.GetSample(i) > 0 && i < endIndex) ++i;\r\n                while (channel.GetSample(i) <= 0 && i < endIndex) ++i;\r\n                // Remember this point\r\n                int lastCrossing = i;\r\n                // Now move forward looking for a peak\r\n                for (var sample = channel.GetSample(i); sample > 0 && i < endIndex; ++i)\r\n                {\r\n                    if (sample > peakValue)\r\n                    {\r\n                        // It's a new high\r\n                        peakValue = sample;\r\n                        result = lastCrossing;\r\n                        shortestDistance = i - lastCrossing;\r\n                    }\r\n                    // ReSharper disable once CompareOfFloatsByEqualityOperator\r\n                    else if (sample == peakValue && (i - lastCrossing) < shortestDistance)\r\n                    {\r\n                        // It's equal to the best peak but closer to the crossing point\r\n                        result = lastCrossing;\r\n                        shortestDistance = i - lastCrossing;\r\n                    }\r\n\r\n                    sample = channel.GetSample(i);\r\n                }\r\n            }\r\n\r\n            return result;\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "LibSidWiz/Triggers/RisingEdgeTrigger.cs",
    "content": "﻿namespace LibSidWiz.Triggers\r\n{\r\n    /// <summary>\r\n    /// Trigger that finds the rising edge of the wave.\r\n    /// This is normally fine for simple waveforms but it can fall down when it sees\r\n    /// waves which cross the centre point more than once.\r\n    /// It also only finds the first rising edge in the sample range, rather than the\r\n    /// one nearest the centre.\r\n    /// </summary>\r\n    internal class RisingEdgeTrigger : ITriggerAlgorithm\r\n    {\r\n        public int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex)\r\n        {\r\n            // We step through the sample and select the first negative -> positive transition\r\n            int result = startIndex;\r\n            while (channel.GetSample(result) > 0 && result < endIndex) ++result;\r\n            while (channel.GetSample(result) <= 0 && result < endIndex) ++result;\r\n            if (result == endIndex)\r\n            {\r\n                // Failed to find anything\r\n                result = -1;\r\n            }\r\n\r\n            return result;\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "LibSidWiz/Triggers/WidestWaveTrigger.cs",
    "content": "﻿namespace LibSidWiz.Triggers\r\n{\r\n    /// <summary>\r\n    /// Finds the widest positive+negative wave in the range\r\n    /// This can get confused by volume changes on SN76489 noise, which it will perceive as a wide wave\r\n    /// </summary>\r\n    // ReSharper disable once UnusedType.Global\r\n    internal class WidestWaveTrigger : ITriggerAlgorithm\r\n    {\r\n        public int GetTriggerPoint(Channel channel, int startIndex, int endIndex, int previousIndex)\r\n        {\r\n            int bestOffset = -1;\r\n            int lastCrossingPoint = endIndex;\r\n            float previousSample = channel.GetSample(startIndex);\r\n            int bestLength = 0;\r\n\r\n            // For each sample...\r\n            for (int i = startIndex + 1; i < endIndex; ++i)\r\n            {\r\n                var sample = channel.GetSample(i);\r\n                if (sample > 0 && previousSample <= 0)\r\n                {\r\n                    // Positive edge\r\n                    int length = i - lastCrossingPoint;\r\n                    if (length > bestLength)\r\n                    {\r\n                        bestLength = length;\r\n                        bestOffset = lastCrossingPoint;\r\n                    }\r\n\r\n                    lastCrossingPoint = i;\r\n                }\r\n\r\n                previousSample = sample;\r\n            }\r\n\r\n            return bestOffset;\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "LibSidWiz/WaveformRenderer.cs",
    "content": "﻿using System;\r\nusing System.Collections.Concurrent;\r\nusing System.Collections.Generic;\r\nusing System.Drawing;\r\nusing System.Drawing.Drawing2D;\r\nusing System.Drawing.Imaging;\r\nusing System.Drawing.Text;\r\nusing System.Linq;\r\nusing System.Runtime.InteropServices;\r\nusing System.Threading;\r\nusing System.Threading.Tasks;\r\nusing LibSidWiz.Outputs;\r\nusing SkiaSharp;\r\nusing SkiaSharp.Views.Desktop;\r\n\r\nnamespace LibSidWiz\r\n{\r\n    /// <summary>\r\n    /// Class responsible for rendering\r\n    /// </summary>\r\n    public class WaveformRenderer\r\n    {\r\n        private readonly List<Channel> _channels = [];\r\n\r\n        public int Width { get; set; }\r\n        public int Height { get; set; }\r\n        public int Columns { get; set; }\r\n        public int SamplingRate { get; set; }\r\n        public int FramesPerSecond { get; set; }\r\n        public Color BackgroundColor { get; set; } = Color.Black;\r\n        public Image BackgroundImage { get; set; }\r\n        public Rectangle RenderingBounds { get; set; }\r\n\r\n        public void AddChannel(Channel channel)\r\n        {\r\n            _channels.Add(channel);\r\n        }\r\n\r\n        public void Render(IList<IGraphicsOutput> outputs, int numThreads, bool verboseLogging)\r\n        {\r\n            int numFrames = (int)(_channels.Max(c => c.SampleCount) * FramesPerSecond / SamplingRate);\r\n            var length = TimeSpan.FromSeconds((double)numFrames / FramesPerSecond);\r\n\r\n            int frameIndex = 0;\r\n            Render(0, numFrames, (bm, rawData) =>\r\n            {\r\n                double fractionComplete = (double) ++frameIndex / numFrames;\r\n                foreach (var output in outputs)\r\n                {\r\n                    output.Write(bm, rawData, fractionComplete, length);\r\n                }\r\n            }, numThreads >= 1 ? numThreads : Environment.ProcessorCount);\r\n        }\r\n\r\n        /// <summary>\r\n        /// Renders a range of frames into the given destination image, calling back the handler for each one\r\n        /// </summary>\r\n        private void Render(int startFrame, int endFrame, Action<SKImage, byte[]> onFrame, int numThreads)\r\n        {\r\n            // Default rendering bounds if not set\r\n            var renderingBounds = RenderingBounds;\r\n            if (renderingBounds.Width == 0 || renderingBounds.Height == 0)\r\n            {\r\n                renderingBounds = new Rectangle(0, 0, Width, Height);\r\n            }\r\n\r\n            // Compute channel bounds\r\n            var numRows = _channels.Count / Columns + (_channels.Count % Columns == 0 ? 0 : 1);\r\n            for (int i = 0; i < _channels.Count; ++i)\r\n            {\r\n                int ChannelX(int column1) => column1 * renderingBounds.Width / Columns + renderingBounds.Left;\r\n                int ChannelY(int row1) => row1 * renderingBounds.Height / numRows + renderingBounds.Top;\r\n\r\n                var channel = _channels[i];\r\n                var column = i % Columns;\r\n                var row = i / Columns;\r\n                // Compute sizes as difference to next one to avoid off-by-one errors\r\n                var x = ChannelX(column);\r\n                var y = ChannelY(row);\r\n                channel.Bounds = new Rectangle(x, y, ChannelX(column + 1) - x, ChannelY(row + 1) - y);\r\n            }\r\n\r\n            // We generate our \"base image\"\r\n            var templateData = new byte[Width * Height * 4];\r\n            GCHandle pinnedArray = GCHandle.Alloc(templateData, GCHandleType.Pinned);\r\n            try\r\n            {\r\n                using var templateImage = new Bitmap(Width, Height, Width * 4, PixelFormat.Format32bppPArgb, pinnedArray.AddrOfPinnedObject());\r\n                GenerateTemplate(templateImage);\r\n\r\n                // We want to do a few things in parallel:\r\n                // 1. For each frame, for each channel, get the previous frame trigger point\r\n                // 2. Find the next trigger point\r\n                // 3. When a frame has all the channels' trigger points, draw it\r\n                // 4. Call the callback in strict frame order\r\n                // #3 is the slowest part, but we'd like to somewhat parallelize parts 1-2 as well.\r\n                // So we do that in a parallel for, in a task, emitting into a queue...\r\n                var queue = new BlockingCollection<FrameInfo>(numThreads);\r\n                // Initialise the \"previous trigger points\"\r\n                var frameSamples = SamplingRate / FramesPerSecond;\r\n                var triggerPoints = new int[_channels.Count];\r\n                for (int channelIndex = 0; channelIndex < _channels.Count; ++channelIndex)\r\n                {\r\n                    triggerPoints[channelIndex] =\r\n                        (int)((long)startFrame * SamplingRate / FramesPerSecond) - frameSamples;\r\n                }\r\n\r\n                Task.Factory.StartNew(() =>\r\n                {\r\n                    for (var frameIndex = startFrame; frameIndex <= endFrame; ++frameIndex)\r\n                    {\r\n                        var info = GetFrameInfo(frameIndex, frameSamples, triggerPoints);\r\n\r\n                        // Add to the queue (may block)\r\n                        queue.Add(info);\r\n                    }\r\n\r\n                    // Signal that this phase is done\r\n                    queue.CompleteAdding();\r\n                });\r\n\r\n                // Then we want to make a bunch of objects to consume from the queue, as it is filled up, in parallel.\r\n                // These are tricky because they will want to emit bitmaps, which need to be passed to the renderers in order.\r\n                // To do that, a simple FIFO is not enough; we want it to also be ordered by frame index and only consume in order.\r\n                var renderedFrames = new ConcurrentDictionary<int, FrameInfo>();\r\n                var frameReadySignal = new AutoResetEvent(false);\r\n                // Then we kick off some parallel threads to do the rendering, consuming from queue and emitting to renderedFrames indexed by frame index\r\n                foreach (var _ in Enumerable.Range(0, numThreads))\r\n                {\r\n                    Task.Factory.StartNew(() =>\r\n                    {\r\n                        // We make a thread-local image to render into\r\n                        // This is the raw data buffer we use to store the generated image.\r\n                        // We need it in this form, so we can pass it to FFMPEG.\r\n                        var rawData = new byte[Width * Height * 4];\r\n                        // We also need to \"pin\" it so the bitmap can be based on it.\r\n                        var innerPinnedArray = GCHandle.Alloc(rawData, GCHandleType.Pinned);\r\n\r\n                        // Prepare the pens and brushes we will use\r\n                        var linePaints = _channels.Select(c => c.LineColor == Color.Transparent || c.LineWidth <= 0\r\n                            ? null\r\n                            : new SKPaint {\r\n                                Color = new SKColor(c.LineColor.R, c.LineColor.G, c.LineColor.B, c.LineColor.A),\r\n                                StrokeWidth = c.LineWidth,\r\n                                IsAntialias = c.SmoothLines,\r\n                                Style = SKPaintStyle.Stroke,\r\n                                StrokeMiter = c.LineWidth,\r\n                                StrokeJoin = SKStrokeJoin.Bevel\r\n                            }).ToList();\r\n                        var fillPaints = _channels.Select(c => c.FillColor == Color.Transparent\r\n                            ? null\r\n                            : new SKPaint {\r\n                                Color = new SKColor(c.FillColor.R, c.FillColor.G, c.FillColor.B, c.FillColor.A),\r\n                                IsAntialias = c.SmoothLines,\r\n                                Style = SKPaintStyle.Fill\r\n                            }).ToList();\r\n\r\n                        try\r\n                        {\r\n                            using var pixmap = new SKPixmap(new SKImageInfo(Width, Height, SKColorType.Bgra8888, SKAlphaType.Opaque), innerPinnedArray.AddrOfPinnedObject());\r\n                            using var image = SKImage.FromPixels(pixmap);\r\n                            using var surface = SKSurface.Create(pixmap.Info, innerPinnedArray.AddrOfPinnedObject());\r\n\r\n                            // Prepare buffers to hold the line coordinates\r\n                            var path = new SKPath();\r\n                            var fillPath = new SKPath();\r\n\r\n                            while (!queue.IsCompleted)\r\n                            {\r\n                                // Get a frame to work on. This may block.\r\n                                var frame = queue.Take();\r\n                                var g = surface.Canvas;\r\n\r\n                                // Copy from the template\r\n                                Buffer.BlockCopy(templateData, 0, rawData, 0, templateData.Length);\r\n\r\n                                // For each channel...\r\n                                for (var channelIndex = 0; channelIndex < _channels.Count; ++channelIndex)\r\n                                {\r\n                                    var channel = _channels[channelIndex];\r\n                                    if (channel.IsEmpty) continue;\r\n\r\n                                    if (!string.IsNullOrEmpty(channel.ErrorMessage))\r\n                                        g.DrawText(\r\n                                            channel.ErrorMessage, \r\n                                            new SKPoint(channel.Bounds.Left + channel.Bounds.Width / 2.0f, channel.Bounds.Top + channel.Bounds.Height / 2.0f), \r\n                                            new SKPaint\r\n                                            {\r\n                                                Typeface = SKTypeface.Default,\r\n                                                Color = SKColors.Red,\r\n                                                TextAlign = SKTextAlign.Center,\r\n                                            });\r\n                                    else if (channel.Loading)\r\n                                        g.DrawText(\r\n                                            \"Loading data...\",\r\n                                            new SKPoint(channel.Bounds.Left + channel.Bounds.Width / 2.0f, channel.Bounds.Top + channel.Bounds.Height / 2.0f), \r\n                                            new SKPaint\r\n                                            {\r\n                                                Typeface = SKTypeface.Default,\r\n                                                Color = SKColors.Green,\r\n                                                TextAlign = SKTextAlign.Center,\r\n                                            });\r\n                                    else if (channel.IsSilent && !channel.RenderIfSilent)\r\n                                        g.DrawText(\r\n                                            \"This channel is silent\",\r\n                                            new SKPoint(channel.Bounds.Left + channel.Bounds.Width / 2.0f, channel.Bounds.Top + channel.Bounds.Height / 2.0f), \r\n                                            new SKPaint\r\n                                            {\r\n                                                Typeface = SKTypeface.Default,\r\n                                                Color = SKColors.Yellow,\r\n                                                TextAlign = SKTextAlign.Center,\r\n                                            });\r\n                                    else\r\n                                        // ReSharper disable once AccessToDisposedClosure\r\n                                        RenderWave(g, channel, frame.ChannelTriggerPoints[channelIndex],\r\n                                            linePaints[channelIndex], fillPaints[channelIndex], path, fillPath, channel.FillBase);\r\n                                }\r\n\r\n                                // We \"lend\" the data to the frame info temporarily\r\n                                frame.Bitmap = image;\r\n                                frame.RawData = rawData;\r\n                                renderedFrames.TryAdd(frame.FrameIndex, frame);\r\n                                // Signal the consuming thread\r\n                                frameReadySignal.Set();\r\n                                // Wait for it to finish consuming\r\n                                frame.BitmapConsumed.WaitOne();\r\n                            }\r\n                        }\r\n                        finally\r\n                        {\r\n                            innerPinnedArray.Free();\r\n                            foreach (var paint in linePaints.Concat(fillPaints))\r\n                            {\r\n                                paint?.Dispose();\r\n                            }\r\n                        }\r\n                    }, TaskCreationOptions.LongRunning);\r\n                }\r\n\r\n                // Finally, we consume the queue\r\n                for (var frameIndex = startFrame; frameIndex < endFrame; frameIndex++)\r\n                {\r\n                    FrameInfo frame;\r\n                    while (!renderedFrames.TryGetValue(frameIndex, out frame))\r\n                    {\r\n                        // Wait for the dictionary to have this index\r\n                        frameReadySignal.WaitOne();\r\n                    }\r\n\r\n                    // Emit the frame\r\n                    onFrame(frame.Bitmap, frame.RawData);\r\n                    frame.BitmapConsumed.Set();\r\n                    renderedFrames.TryRemove(frameIndex, out _);\r\n                }\r\n            }\r\n            finally\r\n            {\r\n                pinnedArray.Free();\r\n            }\r\n        }\r\n\r\n        private FrameInfo GetFrameInfo(int frameIndex, int frameSamples, int[] triggerPoints)\r\n        {\r\n            // Compute the start of the sample window\r\n            int frameIndexSamples = (int)((long)frameIndex * SamplingRate / FramesPerSecond);\r\n            var info = new FrameInfo\r\n            {\r\n                FrameIndex = frameIndex,\r\n                ChannelTriggerPoints = Enumerable.Repeat(frameIndexSamples, _channels.Count).ToList()\r\n            };\r\n            // For each channel...\r\n            for (int channelIndex = 0; channelIndex < _channels.Count; ++channelIndex)\r\n            {\r\n                var channel = _channels[channelIndex];\r\n                if (channel.IsEmpty ||\r\n                    !string.IsNullOrEmpty(channel.ErrorMessage) ||\r\n                    channel.Loading ||\r\n                    channel.IsSilent)\r\n                {\r\n                    // No trigger to find\r\n                    continue;\r\n                }\r\n\r\n                // Compute the \"trigger point\". This will be the centre of our rendering.\r\n                var triggerPoint = channel.GetTriggerPoint(frameIndexSamples, frameSamples,\r\n                    triggerPoints[channelIndex]);\r\n                info.ChannelTriggerPoints[channelIndex] = triggerPoint;\r\n                triggerPoints[channelIndex] = triggerPoint;\r\n            }\r\n\r\n            return info;\r\n        }\r\n\r\n        private class FrameInfo\r\n        {\r\n            public int FrameIndex { get; set; }\r\n            public List<int> ChannelTriggerPoints { get; set; }\r\n            public SKImage Bitmap { get; set; }\r\n            public byte[] RawData { get; set; }\r\n            public AutoResetEvent BitmapConsumed { get; } = new(false);\r\n        }\r\n\r\n        private void GenerateTemplate(Image template)\r\n        {\r\n            // Draw it\r\n            using var g = Graphics.FromImage(template);\r\n            g.SmoothingMode = SmoothingMode.HighQuality;\r\n            g.InterpolationMode = InterpolationMode.HighQualityBicubic;\r\n\r\n            if (BackgroundImage != null)\r\n            {\r\n                // Fill with the background image\r\n                using var attribute = new ImageAttributes();\r\n                attribute.SetWrapMode(WrapMode.TileFlipXY);\r\n                g.DrawImage(\r\n                    BackgroundImage,\r\n                    new Rectangle(0, 0, Width, Height),\r\n                    0,\r\n                    0,\r\n                    BackgroundImage.Width,\r\n                    BackgroundImage.Height,\r\n                    GraphicsUnit.Pixel,\r\n                    attribute);\r\n            }\r\n            else\r\n            {\r\n                // Fill background\r\n                using var brush = new SolidBrush(BackgroundColor);\r\n                g.FillRectangle(brush, -1, -1, Width + 1, Height + 1);\r\n            }\r\n\r\n            foreach (var channel in _channels)\r\n            {\r\n                if (channel.BackgroundColor != Color.Transparent)\r\n                {\r\n                    using var b = new SolidBrush(channel.BackgroundColor);\r\n                    g.FillRectangle(b, channel.Bounds);\r\n                }\r\n\r\n                if (channel.ZeroLineColor != Color.Transparent && channel.ZeroLineWidth > 0)\r\n                {\r\n                    using var pen = new Pen(channel.ZeroLineColor, channel.ZeroLineWidth);\r\n                    // Draw the zero line\r\n                    g.DrawLine(\r\n                        pen,\r\n                        channel.Bounds.Left,\r\n                        channel.Bounds.Top + channel.Bounds.Height / 2,\r\n                        channel.Bounds.Right,\r\n                        channel.Bounds.Top + channel.Bounds.Height / 2);\r\n                }\r\n\r\n                if (channel.BorderWidth > 0 && channel.BorderColor != Color.Transparent)\r\n                {\r\n                    using var pen = new Pen(channel.BorderColor, channel.BorderWidth);\r\n                    if (channel.BorderEdges)\r\n                    {\r\n                        // We want all edges to show equally.\r\n                        // To achieve this, we need to artificially pull the edges in 1px on the right and bottom.\r\n                        g.DrawRectangle(\r\n                            pen,\r\n                            channel.Bounds.Left,\r\n                            channel.Bounds.Top,\r\n                            channel.Bounds.Width - (channel.Bounds.Right == RenderingBounds.Right ? 1 : 0),\r\n                            channel.Bounds.Height -\r\n                            (channel.Bounds.Bottom == RenderingBounds.Bottom ? 1 : 0));\r\n                    }\r\n                    else\r\n                    {\r\n                        // We want to draw all lines which are not on the rendering bounds\r\n                        if (channel.Bounds.Left != RenderingBounds.Left)\r\n                        {\r\n                            g.DrawLine(pen, channel.Bounds.Left, channel.Bounds.Top, channel.Bounds.Left,\r\n                                channel.Bounds.Bottom);\r\n                        }\r\n\r\n                        if (channel.Bounds.Top != RenderingBounds.Top)\r\n                        {\r\n                            g.DrawLine(pen, channel.Bounds.Left, channel.Bounds.Top, channel.Bounds.Right,\r\n                                channel.Bounds.Top);\r\n                        }\r\n\r\n                        if (channel.Bounds.Right != RenderingBounds.Right)\r\n                        {\r\n                            g.DrawLine(pen, channel.Bounds.Right, channel.Bounds.Top, channel.Bounds.Right,\r\n                                channel.Bounds.Bottom);\r\n                        }\r\n\r\n                        if (channel.Bounds.Bottom != RenderingBounds.Bottom)\r\n                        {\r\n                            g.DrawLine(pen, channel.Bounds.Left, channel.Bounds.Bottom,\r\n                                channel.Bounds.Right, channel.Bounds.Bottom);\r\n                        }\r\n                    }\r\n                }\r\n\r\n                if (channel.LabelFont != null && channel.LabelColor != Color.Transparent)\r\n                {\r\n                    g.TextRenderingHint = TextRenderingHint.AntiAlias;\r\n                    using var brush = new SolidBrush(channel.LabelColor);\r\n                    var stringFormat = new StringFormat();\r\n                    var layoutRectangle = new RectangleF(\r\n                        channel.Bounds.Left + channel.LabelMargins.Left,\r\n                        channel.Bounds.Top + channel.LabelMargins.Top,\r\n                        channel.Bounds.Width - channel.LabelMargins.Left - channel.LabelMargins.Right,\r\n                        channel.Bounds.Height - channel.LabelMargins.Top - channel.LabelMargins.Bottom);\r\n                    switch (channel.LabelAlignment)\r\n                    {\r\n                        case ContentAlignment.TopLeft:\r\n                            stringFormat.Alignment = StringAlignment.Near;\r\n                            stringFormat.LineAlignment = StringAlignment.Near;\r\n                            break;\r\n                        case ContentAlignment.TopCenter:\r\n                            stringFormat.Alignment = StringAlignment.Center;\r\n                            stringFormat.LineAlignment = StringAlignment.Near;\r\n                            break;\r\n                        case ContentAlignment.TopRight:\r\n                            stringFormat.Alignment = StringAlignment.Far;\r\n                            stringFormat.LineAlignment = StringAlignment.Near;\r\n                            break;\r\n                        case ContentAlignment.MiddleLeft:\r\n                            stringFormat.Alignment = StringAlignment.Near;\r\n                            stringFormat.LineAlignment = StringAlignment.Center;\r\n                            break;\r\n                        case ContentAlignment.MiddleCenter:\r\n                            stringFormat.Alignment = StringAlignment.Center;\r\n                            stringFormat.LineAlignment = StringAlignment.Center;\r\n                            break;\r\n                        case ContentAlignment.MiddleRight:\r\n                            stringFormat.Alignment = StringAlignment.Far;\r\n                            stringFormat.LineAlignment = StringAlignment.Center;\r\n                            break;\r\n                        case ContentAlignment.BottomLeft:\r\n                            stringFormat.Alignment = StringAlignment.Near;\r\n                            stringFormat.LineAlignment = StringAlignment.Far;\r\n                            break;\r\n                        case ContentAlignment.BottomCenter:\r\n                            stringFormat.Alignment = StringAlignment.Center;\r\n                            stringFormat.LineAlignment = StringAlignment.Far;\r\n                            break;\r\n                        case ContentAlignment.BottomRight:\r\n                            stringFormat.Alignment = StringAlignment.Far;\r\n                            stringFormat.LineAlignment = StringAlignment.Far;\r\n                            break;\r\n                        default:\r\n                            throw new ArgumentOutOfRangeException();\r\n                    }\r\n\r\n                    g.DrawString(channel.Label, channel.LabelFont, brush, layoutRectangle, stringFormat);\r\n                }\r\n            }\r\n        }\r\n\r\n        private static void RenderWave(SKCanvas g, Channel channel, int triggerPoint, SKPaint linePaint,\r\n            SKPaint fillPaint, SKPath path, SKPath fillPath, double fillBase)\r\n        {\r\n            // And the initial sample index\r\n            var leftmostSampleIndex = triggerPoint - channel.ViewWidthInSamples / 2;\r\n\r\n            float xOffset = channel.Bounds.Left;\r\n            float xScale = (float) channel.Bounds.Width / channel.ViewWidthInSamples;\r\n            float yOffset = channel.Bounds.Top + channel.Bounds.Height * 0.5f;\r\n            float yScale = -channel.Bounds.Height * 0.5f;\r\n            path.Rewind();\r\n            for (var i = 0; i < channel.ViewWidthInSamples; ++i)\r\n            {\r\n                var sampleValue = channel.GetSample(leftmostSampleIndex + i, false);\r\n                var x = xOffset + i * xScale;\r\n                var y = yOffset + sampleValue * yScale;\r\n                if (channel.Clip)\r\n                {\r\n                    y = Math.Min(Math.Max(y, channel.Bounds.Top), channel.Bounds.Bottom);\r\n                }\r\n                if (i == 0)\r\n                {\r\n                    path.MoveTo(x, y);\r\n                }\r\n                else\r\n                {\r\n                    path.LineTo(x, y);\r\n                }\r\n            }\r\n\r\n            // Then draw them all in one go...\r\n            // Draw the fill \"under\" the line\r\n            if (fillPaint != null)\r\n            {\r\n                // We need to add points to complete the path\r\n                // We compute the Y position of this line. -0.5 scales -1..1 to bottom..top.\r\n                var baseY = (float)(yOffset + channel.Bounds.Height * -0.5 * fillBase);\r\n                fillPath.Rewind();\r\n                fillPath.AddPath(path);\r\n                fillPath.LineTo(channel.Bounds.Right, baseY);\r\n                fillPath.LineTo(channel.Bounds.Left, baseY);\r\n                g.DrawPath(fillPath, fillPaint);\r\n            }\r\n\r\n            if (linePaint != null)\r\n            {\r\n                g.DrawPath(path, linePaint);\r\n            }\r\n        }\r\n\r\n        /// <summary>\r\n        /// Version for rendering a single frame for previewing\r\n        /// </summary>\r\n        public Bitmap RenderFrame(float position = 0)\r\n        {\r\n            var frameIndex = _channels.Count > 0\r\n                ? (int) (position * _channels.Max(c => c.SampleCount) * FramesPerSecond / SamplingRate)\r\n                : 0;\r\n            Bitmap bitmap = null;\r\n            Render(frameIndex, frameIndex + 1, (bm, _) =>\r\n            {\r\n                bitmap = bm.ToBitmap();\r\n            }, 1);\r\n            return bitmap;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "LibVgm/Gd3Tag.cs",
    "content": "using System;\r\nusing System.Collections.Generic;\r\nusing System.IO;\r\nusing System.Text;\r\n\r\nnamespace LibVgm\r\n{\r\n    public class Gd3Tag\r\n    {\r\n        public struct MultiLanguageTag\r\n        {\r\n            public string English { get; set; }\r\n            public string Japanese { get; set; }\r\n\r\n            public readonly override string ToString()\r\n            {\r\n                return English.Length > 0 ? Japanese.Length > 0 ? $\"{English} ({Japanese})\" : $\"{English}\" : \"\";\r\n            }\r\n        }\r\n\r\n        // ReSharper disable MemberCanBePrivate.Global\r\n        public string Ident { get; private set; }\r\n        public decimal Version { get; private set; }\r\n        \r\n        public MultiLanguageTag Title { get; set; }\r\n        public MultiLanguageTag Game { get; set; }\r\n        public MultiLanguageTag System { get; set; }\r\n        public MultiLanguageTag Composer { get; set; }\r\n        public string Date { get; set; }\r\n        public string Ripper { get; set; }\r\n        public string Notes { get; set; }\r\n        // ReSharper restore MemberCanBePrivate.Global\r\n\r\n        public static Gd3Tag LoadFromVgm(string filename)\r\n        {\r\n            // Open the stream\r\n            using var s = new OptionalGzipStream(filename);\r\n            using var r = new BinaryReader(s, Encoding.ASCII);\r\n            r.ReadBytes(0x14);\r\n            var offset = r.ReadUInt32() + 0x14;\r\n            if (offset == 0)\r\n            {\r\n                // No tag\r\n                return null;\r\n            }\r\n\r\n            if (offset > s.Length - 8 - 11*2)\r\n            {\r\n                throw new InvalidDataException(\"Not enough room in file for GD3 offset\");\r\n            }\r\n\r\n            var result = new Gd3Tag();\r\n            result.Parse(s, offset);\r\n            return result;\r\n        }\r\n\r\n        public void Parse(Stream s, uint offset)\r\n        {\r\n            var tags = new List<string>();\r\n            using (var r = new BinaryReader(s, Encoding.Unicode, true))\r\n            {\r\n                s.Seek(offset, SeekOrigin.Begin);\r\n                Ident = Encoding.ASCII.GetString(r.ReadBytes(4));\r\n                if (Ident != \"Gd3 \")\r\n                {\r\n                    throw new InvalidDataException(\"GD3 header not found\");\r\n                }\r\n\r\n                var version = r.ReadUInt32();\r\n                // BCD to integer\r\n                int scaled = 0;\r\n                int factor = 1;\r\n                for (int i = 0; i < 8; ++i)\r\n                {\r\n                    var digit = (int) version & 0xf;\r\n                    scaled += digit * factor;\r\n                    version >>= 4;\r\n                    factor *= 10;\r\n                }\r\n\r\n                Version = (decimal) scaled / 100;\r\n\r\n                if (Version >= 2.00m)\r\n                {\r\n                    throw new Exception($\"GD3 version {Version} not supported\");\r\n                }\r\n\r\n                var length = r.ReadUInt32();\r\n                if (s.Length - s.Position > length)\r\n                {\r\n                    throw new Exception(\"File not big enough for GD3 data\");\r\n                }\r\n\r\n                // We read out 11 UCS-2 strings\r\n                var str = \"\";\r\n                for (int i = 0; i < 11; ++i)\r\n                {\r\n                    for (;;)\r\n                    {\r\n                        var c = r.ReadChar();\r\n                        if (c == '\\0')\r\n                        {\r\n                            tags.Add(str);\r\n                            str = \"\";\r\n                            break;\r\n                        }\r\n\r\n                        str += c;\r\n                    }\r\n                }\r\n            }\r\n\r\n            // We then put them into our properties\r\n            Title = new MultiLanguageTag {English = tags[0], Japanese = tags[1]};\r\n            Game = new MultiLanguageTag {English = tags[2], Japanese = tags[3]};\r\n            System = new MultiLanguageTag {English = tags[4], Japanese = tags[5]};\r\n            Composer = new MultiLanguageTag {English = tags[6], Japanese = tags[7]};\r\n            Date = tags[8];\r\n            Ripper = tags[9];\r\n            Notes = tags[10];\r\n        }\r\n\r\n        public override string ToString()\r\n        {\r\n            var title = Title.ToString();\r\n            var game = Game.ToString();\r\n            var system = System.English;\r\n            var composer = Composer.ToString();\r\n            \r\n            var sb = new StringBuilder();\r\n\r\n            // Track Title - Game - System (Date)\r\n            // Composer\r\n            // Ripped by Ripper\r\n\r\n            if (title.Length > 0) sb.Append(title);\r\n            if (game.Length > 0) sb.Append($\" – {game}\");\r\n            if (system.Length > 0) sb.Append($\" – {system}\");\r\n            if (Date.Length > 0) sb.Append($\" ({Date})\");\r\n            if (composer.Length > 0)\r\n            {\r\n                sb.AppendLine();\r\n                sb.Append(composer);\r\n            }\r\n            if (Ripper.Length > 0)\r\n            {\r\n                sb.AppendLine();\r\n                sb.Append($\"Ripped by {Ripper}\");\r\n            }\r\n\r\n            return sb.ToString();\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "LibVgm/LibVgm.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\r\n  <PropertyGroup>\r\n    <TargetFramework>netstandard2.0</TargetFramework>\r\n    <LangVersion>latest</LangVersion>\r\n    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>\r\n  </PropertyGroup>\r\n</Project>"
  },
  {
    "path": "LibVgm/OptionalGzipStream.cs",
    "content": "﻿using System.IO;\r\nusing System.IO.Compression;\r\n\r\nnamespace LibVgm\r\n{\r\n    /// <summary>\r\n    /// Stream class which transparently supports GZipped or uncompressed files\r\n    /// </summary>\r\n    internal class OptionalGzipStream : Stream\r\n    {\r\n        private readonly Stream _stream;\r\n\r\n        public OptionalGzipStream(string filename)\r\n        {\r\n            var fileStream = new FileStream(filename, FileMode.Open);\r\n            // Check if it's GZipped\r\n            bool needGzip = fileStream.ReadByte() == 0x1f && fileStream.ReadByte() == 0x8b;\r\n            fileStream.Seek(0, SeekOrigin.Begin);\r\n            if (needGzip)\r\n            {\r\n                using var gZipStream = new GZipStream(fileStream, CompressionMode.Decompress);\r\n                _stream = new MemoryStream();\r\n                gZipStream.CopyTo(_stream);\r\n                _stream.Seek(0, SeekOrigin.Begin);\r\n            }\r\n            else\r\n            {\r\n                _stream = fileStream;\r\n            }\r\n        }\r\n\r\n        public override void Flush() => _stream.Flush();\r\n        public override long Seek(long offset, SeekOrigin origin) => _stream.Seek(offset, origin);\r\n        public override void SetLength(long value) => _stream.SetLength(value);\r\n        public override int Read(byte[] buffer, int offset, int count) => _stream.Read(buffer, offset, count);\r\n        public override void Write(byte[] buffer, int offset, int count) => _stream.Write(buffer, offset, count);\r\n\r\n        public override bool CanRead => _stream.CanRead;\r\n        public override bool CanSeek => _stream.CanSeek;\r\n        public override bool CanWrite => _stream.CanWrite;\r\n        public override long Length => _stream.Length;\r\n        public override long Position\r\n        {\r\n            get => _stream.Position;\r\n            set => _stream.Position = value;\r\n        }\r\n\r\n        protected override void Dispose(bool disposing)\r\n        {\r\n            _stream?.Dispose();\r\n            base.Dispose(disposing);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "LibVgm/VgmFile.cs",
    "content": "﻿using System;\r\nusing System.Collections.Generic;\r\nusing System.IO;\r\nusing System.Text;\r\n\r\nnamespace LibVgm\r\n{\r\n    public class VgmFile: IDisposable\r\n    {\r\n        // It's painful to seek in GZipped streams, so we don't bother...\r\n        private readonly MemoryStream _stream;\r\n\r\n        // ReSharper disable MemberCanBePrivate.Global\r\n        // ReSharper disable UnusedAutoPropertyAccessor.Global\r\n        // ReSharper disable AutoPropertyCanBeMadeGetOnly.Global\r\n        // ReSharper disable MemberCanBeProtected.Global\r\n        public VgmHeader Header { get; }\r\n        public Gd3Tag Gd3Tag { get; }\r\n\r\n        public class VgmHeader\r\n        {\r\n            public string Ident { get; set; }\r\n            public uint EndOfFileOffset { get; set; }\r\n            public decimal Version { get; set; }\r\n            public uint Sn76489Clock { get; set; }\r\n            public uint Ym2413Clock { get; set; }\r\n            public uint Gd3Offset { get; set; }\r\n            public uint TotalSamples { get; set; }\r\n            public uint LoopOffset { get; set; }\r\n            public uint LoopSamples { get; set; }\r\n            public uint Rate { get; set; }\r\n            public uint SnFeedback { get; set; }\r\n            public uint SnWidth { get; set; }\r\n            public uint SnFlag { get; set; }\r\n            public uint Ym2612Clock { get; set; }\r\n            public uint Ym2151Clock { get; set; }\r\n            public uint DataOffset { get; set; } = 0x40;\r\n            public uint SegaPcmClock { get; set; }\r\n            public uint SpcmInterface { get; set; }\r\n            public uint Rf5C68Clock { get; set; }\r\n            public uint Ym2203Clock { get; set; }\r\n            public uint Ym2608Clock { get; set; }\r\n            public uint Ym2610BClock { get; set; }\r\n            public uint Ym3812Clock { get; set; }\r\n            public uint Ym3526Clock { get; set; }\r\n            public uint Y8950Clock { get; set; }\r\n            public uint Ymf262Clock { get; set; }\r\n            public uint Ymf278BClock { get; set; }\r\n            public uint Ymf271Clock { get; set; }\r\n            public uint Ymz280BClock { get; set; }\r\n            public uint Rf5C164Clock { get; set; }\r\n            public uint PwmClock { get; set; }\r\n            public uint Ay8910Clock { get; set; }\r\n            public uint AyType { get; set; }\r\n            public uint AyFlags { get; set; }\r\n            public uint VolumeModifier { get; set; }\r\n            public uint LoopBase { get; set; }\r\n            public uint LoopModifier { get; set; }\r\n            public uint GbDmgClock { get; set; }\r\n            public uint NesApuClock { get; set; }\r\n            public uint MultiPcmClock { get; set; }\r\n            public uint Upd7759Clock { get; set; }\r\n            public uint Okim6258Clock { get; set; }\r\n            public uint Okim6258Flags { get; set; }\r\n            public uint K054539Flags { get; set; }\r\n            public uint C140ChipType { get; set; }\r\n            public uint Okim6295Clock { get; set; }\r\n            public uint K051649Clock { get; set; }\r\n            public uint K054539Clock { get; set; }\r\n            public uint HuC6280Clock { get; set; }\r\n            public uint C140Clock { get; set; }\r\n            public uint K053260Clock { get; set; }\r\n            public uint PokeyClock { get; set; }\r\n            public uint QSoundClock { get; set; }\r\n            public uint ScspClock { get; set; }\r\n            public uint ExtraHeaderOffset { get; set; }\r\n            public uint WonderSwanClock { get; set; }\r\n            public uint VsuClock { get; set; }\r\n            public uint Saa1099Clock { get; set; }\r\n            public uint Es5503Clock { get; set; }\r\n            public uint Es5506Clock { get; set; }\r\n            public uint Es5503Channels { get; set; }\r\n            public uint Es5506Channels { get; set; }\r\n            public uint X1010Clock { get; set; }\r\n            public uint C352Clock { get; set; }\r\n            public uint Ga20Clock { get; set; }\r\n\r\n            internal VgmHeader()\r\n            {\r\n                Ident = \"Vgm \";\r\n                Version = 1.10m;\r\n            }\r\n\r\n            internal void Parse(Stream s)\r\n            {\r\n                using var r = new BinaryReader(s, Encoding.ASCII, true);\r\n                \r\n                // VGM 1.00\r\n                Ident = string.Concat(r.ReadChars(4));\r\n                EndOfFileOffset = r.ReadUInt32();\r\n                if (EndOfFileOffset == 0)\r\n                {\r\n                    EndOfFileOffset = (uint) s.Length;\r\n                }\r\n                else\r\n                {\r\n                    EndOfFileOffset += 4; // Make absolute\r\n                }\r\n                var version = r.ReadUInt32();\r\n                // BCD to integer\r\n                int scaled = 0;\r\n                int factor = 1;\r\n                for (int i = 0; i < 8; ++i)\r\n                {\r\n                    var digit = (int) version & 0xf;\r\n                    scaled += digit * factor;\r\n                    version >>= 4;\r\n                    factor *= 10;\r\n                }\r\n\r\n                Version = (decimal) scaled / 100;\r\n                Sn76489Clock = r.ReadUInt32();\r\n                Ym2413Clock = r.ReadUInt32();\r\n                Gd3Offset = r.ReadUInt32();\r\n                if (Gd3Offset != 0)\r\n                {\r\n                    Gd3Offset += 0x14; // Make absolute\r\n                }\r\n                TotalSamples = r.ReadUInt32();\r\n                LoopOffset = r.ReadUInt32();\r\n                if (LoopOffset > 0)\r\n                {\r\n                    LoopOffset += 0x1c; // Make absolute\r\n                }\r\n                LoopSamples = r.ReadUInt32();\r\n                if (Version > 1.01m)\r\n                {\r\n                    Rate = r.ReadUInt32();\r\n                }\r\n\r\n                // VGM 1.10\r\n                if (Version > 1.10m)\r\n                {\r\n                    SnFeedback = r.ReadUInt16();\r\n                    SnWidth = r.ReadByte();\r\n                    if (Version > 1.51m)\r\n                    {\r\n                        SnFlag = r.ReadByte();\r\n                    }\r\n                    else\r\n                    {\r\n                        r.ReadByte();\r\n                    }\r\n\r\n                    Ym2612Clock = r.ReadUInt32();\r\n                    Ym2151Clock = r.ReadUInt32();\r\n\r\n                    if (Version > 1.50m)\r\n                    {\r\n                        DataOffset = r.ReadUInt32();\r\n                        if (DataOffset == 0)\r\n                        {\r\n                            DataOffset = 0x40; // Assume default\r\n                        }\r\n                        else\r\n                        {\r\n                            DataOffset += 0x34; // Make absolute\r\n                        }\r\n\r\n                        if (Version > 1.51m)\r\n                        {\r\n                            SegaPcmClock = r.ReadUInt32();\r\n                            SpcmInterface = r.ReadUInt32();\r\n                            Rf5C68Clock = r.ReadUInt32();\r\n                            Ym2203Clock = r.ReadUInt32();\r\n                            Ym2608Clock = r.ReadUInt32();\r\n                            Ym2610BClock = r.ReadUInt32();\r\n                            Ym3812Clock = r.ReadUInt32();\r\n                            Ym3526Clock = r.ReadUInt32();\r\n                            Y8950Clock = r.ReadUInt32();\r\n                            Ymf262Clock = r.ReadUInt32();\r\n                            Ymf278BClock = r.ReadUInt32();\r\n                            Ymf271Clock = r.ReadUInt32();\r\n                            Ymz280BClock = r.ReadUInt32();\r\n                            Rf5C164Clock = r.ReadUInt32();\r\n                            PwmClock = r.ReadUInt32();\r\n                            Ay8910Clock = r.ReadUInt32();\r\n                            var n = r.ReadUInt32();\r\n                            AyType = n >> 24;\r\n                            AyFlags = n & 0xffffff;\r\n\r\n                            if (Version > 1.60m)\r\n                            {\r\n                                VolumeModifier = r.ReadByte();\r\n                                r.ReadByte();\r\n                                LoopBase = r.ReadByte();\r\n                            }\r\n                            else\r\n                            {\r\n                                r.ReadBytes(3);\r\n                            }\r\n\r\n                            LoopModifier = r.ReadByte();\r\n                            if (Version > 1.61m)\r\n                            {\r\n                                GbDmgClock = r.ReadUInt32();\r\n                                NesApuClock = r.ReadUInt32();\r\n                                MultiPcmClock = r.ReadUInt32();\r\n                                Upd7759Clock = r.ReadUInt32();\r\n                                Okim6258Clock = r.ReadUInt32();\r\n                                Okim6258Flags = r.ReadByte();\r\n                                K054539Flags = r.ReadByte();\r\n                                C140ChipType = r.ReadByte();\r\n                                r.ReadByte();\r\n                                Okim6295Clock = r.ReadUInt32();\r\n                                K051649Clock = r.ReadUInt32();\r\n                                K054539Clock = r.ReadUInt32();\r\n                                HuC6280Clock = r.ReadUInt32();\r\n                                C140Clock = r.ReadUInt32();\r\n                                K053260Clock = r.ReadUInt32();\r\n                                PokeyClock = r.ReadUInt32();\r\n                                QSoundClock = r.ReadUInt32();\r\n                                if (Version > 1.70m)\r\n                                {\r\n                                    if (Version > 1.71m)\r\n                                    {\r\n                                        ScspClock = r.ReadUInt32();\r\n                                    }\r\n                                    else\r\n                                    {\r\n                                        r.ReadUInt32();\r\n                                    }\r\n\r\n                                    ExtraHeaderOffset = (uint) r.BaseStream.Position + r.ReadUInt32();\r\n\r\n                                    if (Version > 1.71m)\r\n                                    {\r\n                                        WonderSwanClock = r.ReadUInt32();\r\n                                        VsuClock = r.ReadUInt32();\r\n                                        Saa1099Clock = r.ReadUInt32();\r\n                                        Es5503Clock = r.ReadUInt32();\r\n                                        Es5506Clock = r.ReadUInt32();\r\n                                        Es5503Channels = r.ReadUInt16();\r\n                                        Es5506Channels = r.ReadByte();\r\n                                        r.ReadByte();\r\n                                        X1010Clock = r.ReadUInt32();\r\n                                        C352Clock = r.ReadUInt32();\r\n                                        Ga20Clock = r.ReadUInt32();\r\n                                    }\r\n                                }\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        public interface ICommand\r\n        {\r\n        }\r\n\r\n        public class GenericCommand: ICommand\r\n        {\r\n            public byte[] Values { get; set; }\r\n\r\n            public GenericCommand(BinaryReader reader, int count)\r\n            {\r\n                Values = reader.ReadBytes(count);\r\n            }\r\n\r\n            public override string ToString() => $\"Generic command {Values.Length} bytes\";\r\n        }\r\n\r\n        public class AddressDataCommand: ICommand\r\n        {\r\n            public byte Type { get; }\r\n            public byte Address { get; set; }\r\n            public byte Data { get; set; }\r\n\r\n            public AddressDataCommand(BinaryReader reader, byte type)\r\n            {\r\n                Type = type;\r\n                Address = reader.ReadByte();\r\n                Data = reader.ReadByte();\r\n            }\r\n\r\n            public override string ToString() => $\"Type {Type:X} Address {Address:X} Data {Data:X}\";\r\n        }\r\n\r\n        public class WaitCommand : ICommand\r\n        {\r\n            public int Ticks { get; set; }\r\n\r\n            public WaitCommand(BinaryReader reader, byte type)\r\n            {\r\n                switch (type)\r\n                {\r\n                    case 0x61:\r\n                        Ticks = reader.ReadUInt16();\r\n                        break;\r\n                    case 0x62: \r\n                        Ticks = 735; \r\n                        break;\r\n                    case 0x63: \r\n                        Ticks = 882; \r\n                        break;\r\n                    case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: \r\n                    case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f:\r\n                        Ticks = (type & 0xf) + 1;\r\n                        break;\r\n                }\r\n            }\r\n\r\n            public override string ToString() => $\"Wait {Ticks} samples\";\r\n        }\r\n\r\n        public class SampleWaitCommand : WaitCommand\r\n        {\r\n            public SampleWaitCommand(BinaryReader reader, byte type) : base(reader, type)\r\n            {\r\n                // Sample waits are one less\r\n                --Ticks;\r\n            }\r\n            public override string ToString() => $\"Wait {Ticks} samples and play sample\";\r\n        }\r\n\r\n        public class StopCommand : ICommand\r\n        {\r\n            public override string ToString() => \"Stop\";\r\n        }\r\n\r\n        public class DataBlock : ICommand\r\n        {\r\n            public byte BlockType { get; set; }\r\n            public byte[] Data { get; set; }\r\n\r\n            public DataBlock(BinaryReader reader)\r\n            {\r\n                reader.ReadByte(); // Skip the stop command\r\n                BlockType = reader.ReadByte();\r\n                var count = reader.ReadInt32();\r\n                Data = reader.ReadBytes(count);\r\n            }\r\n            public override string ToString() => $\"Data block: type {BlockType:X} size {Data}\";\r\n        }\r\n\r\n        public class PcmRamWrite : ICommand\r\n        {\r\n            public byte ChipType { get; set; }\r\n            public int ReadOffset { get; set; }\r\n            public int Count { get; set; }\r\n            public int WriteOffset { get; set; }\r\n\r\n            public PcmRamWrite(BinaryReader reader)\r\n            {\r\n                reader.ReadByte(); // Skip the stop command\r\n                ChipType = reader.ReadByte();\r\n                ReadOffset = reader.ReadUInt16() + reader.ReadByte() << 16; // 24-bit read\r\n                WriteOffset = reader.ReadUInt16() + reader.ReadByte() << 16; // 24-bit read\r\n                Count = reader.ReadUInt16() + reader.ReadByte() << 16; // 24-bit read\r\n                if (Count == 0)\r\n                {\r\n                    Count = 1 << 24;\r\n                }\r\n            }\r\n        }\r\n\r\n        public abstract class DacStreamCommand : ICommand\r\n        {\r\n            public byte StreamId { get; set; }\r\n\r\n            protected DacStreamCommand(BinaryReader reader)\r\n            {\r\n                StreamId = reader.ReadByte();\r\n            }\r\n        }\r\n\r\n        public class DacStreamSetupCommand : DacStreamCommand\r\n        {\r\n            public DacStreamSetupCommand(BinaryReader reader) : base(reader)\r\n            {\r\n                ChipType = reader.ReadByte();\r\n                Port = reader.ReadByte();\r\n                Command = reader.ReadByte();\r\n            }\r\n\r\n            public byte Command { get; set; }\r\n\r\n            public byte Port { get; set; }\r\n\r\n            public byte ChipType { get; set; }\r\n        }\r\n\r\n        public class DacStreamDataCommand: DacStreamCommand\r\n        {\r\n            public DacStreamDataCommand(BinaryReader reader) : base(reader)\r\n            {\r\n                BankId = reader.ReadByte();\r\n                StepSize = reader.ReadByte();\r\n                StepBase = reader.ReadByte();\r\n            }\r\n\r\n            public byte StepBase { get; set; }\r\n\r\n            public byte StepSize { get; set; }\r\n\r\n            public byte BankId { get; set; }\r\n        }\r\n\r\n        public class DacStreamFrequencyCommand: DacStreamCommand\r\n        {\r\n            public DacStreamFrequencyCommand(BinaryReader reader) : base(reader)\r\n            {\r\n                Frequency = reader.ReadUInt32();\r\n            }\r\n\r\n            public uint Frequency { get; set; }\r\n        }\r\n\r\n        public class DacStreamStartCommand: DacStreamCommand\r\n        {\r\n            public DacStreamStartCommand(BinaryReader reader) : base(reader)\r\n            {\r\n                Offset = reader.ReadUInt32();\r\n                LengthMode = reader.ReadByte();\r\n                Count = reader.ReadUInt32();\r\n            }\r\n\r\n            public uint Count { get; set; }\r\n\r\n            public byte LengthMode { get; set; }\r\n\r\n            public uint Offset { get; set; }\r\n        }\r\n\r\n        public class DacStreamStopCommand : DacStreamCommand\r\n        {\r\n            public DacStreamStopCommand(BinaryReader reader) : base(reader) {}\r\n        }\r\n        public class DacStreamFastStartCommand : DacStreamCommand\r\n        {\r\n            public DacStreamFastStartCommand(BinaryReader reader) : base(reader)\r\n            {\r\n                BlockId = reader.ReadUInt16();\r\n                Flags = reader.ReadByte();\r\n            }\r\n\r\n            public byte Flags { get; set; }\r\n\r\n            public ushort BlockId { get; set; }\r\n        }\r\n        // ReSharper restore MemberCanBeProtected.Global\r\n        // ReSharper restore UnusedAutoPropertyAccessor.Global\r\n        // ReSharper restore MemberCanBePrivate.Global\r\n        // ReSharper restore AutoPropertyCanBeMadeGetOnly.Global\r\n\r\n        // ReSharper disable once UnusedMember.Global\r\n        public VgmFile()\r\n        {\r\n            // Empty file\r\n            _stream = new MemoryStream();\r\n            Header = new VgmHeader();\r\n            Gd3Tag = new Gd3Tag();\r\n        }\r\n\r\n        public VgmFile(string filename)\r\n        {\r\n            _stream = new MemoryStream();\r\n            Header = new VgmHeader();\r\n            Gd3Tag = new Gd3Tag();\r\n\r\n            LoadFromFile(filename);\r\n        }\r\n\r\n        private void LoadFromFile(string filename)\r\n        {\r\n            // We copy all the data into a memory stream to allow seeking\r\n            using (var s = new OptionalGzipStream(filename))\r\n            {\r\n                s.CopyTo(_stream);\r\n                _stream.Seek(0, SeekOrigin.Begin);\r\n            }\r\n\r\n            // We parse the header\r\n            Header.Parse(_stream);\r\n\r\n            // And the GD3 tag, if present\r\n            if (Header.Gd3Offset != 0)\r\n            {\r\n                Gd3Tag.Parse(_stream, Header.Gd3Offset);\r\n            }\r\n\r\n        }\r\n\r\n        public IEnumerable<ICommand> Commands()\r\n        {\r\n            // Seek to the start\r\n            _stream.Seek(Header.DataOffset, SeekOrigin.Begin);\r\n\r\n            using var reader = new BinaryReader(_stream, Encoding.Default, true);\r\n            while (_stream.Position < _stream.Length)\r\n            {\r\n                //var b = reader.ReadByte();\r\n\r\n                switch (reader.ReadByte())\r\n                {\r\n                    case <= 0x2f:\r\n                        // Unhandled\r\n                        continue;\r\n                    case <= 0x3f:\r\n                        yield return new GenericCommand(reader, 1); // Reserved range\r\n                        break;\r\n                    case <= 0x4e:\r\n                        yield return new GenericCommand(reader, 2); // Reserved range\r\n                        break;\r\n                    case <= 0x50:\r\n                        yield return new GenericCommand(reader, 1); // GG stereo or PSG\r\n                        break;\r\n                    case var b and <= 0x5f:\r\n                        yield return new AddressDataCommand(reader, b); // FM chips\r\n                        break;\r\n                    case 0x60:\r\n                        // Unhandled\r\n                        continue;\r\n                    case var b and <= 0x63:\r\n                        yield return new WaitCommand(reader, b);\r\n                        break;\r\n                    case <= 0x65:\r\n                        // Unhandled\r\n                        continue;\r\n                    case 0x66:\r\n                        yield return new StopCommand();\r\n                        yield break;\r\n                    case 0x67:\r\n                        yield return new DataBlock(reader);\r\n                        break;\r\n                    case 0x68:\r\n                        yield return new PcmRamWrite(reader);\r\n                        break;\r\n                    case < 0x6f:\r\n                        // Unhandled\r\n                        continue;\r\n                    case var b and >= 0x70 and <= 0x7f:\r\n                        yield return new WaitCommand(reader, b);\r\n                        break;\r\n                    case var b and <= 0x8f:\r\n                        yield return new SampleWaitCommand(reader, b);\r\n                        break;\r\n                    case 0x90:\r\n                        yield return new DacStreamSetupCommand(reader);\r\n                        break;\r\n                    case 0x91:\r\n                        yield return new DacStreamDataCommand(reader);\r\n                        break;\r\n                    case 0x92:\r\n                        yield return new DacStreamFrequencyCommand(reader);\r\n                        break;\r\n                    case 0x93:\r\n                        yield return new DacStreamStartCommand(reader);\r\n                        break;\r\n                    case 0x94:\r\n                        yield return new DacStreamStopCommand(reader);\r\n                        break;\r\n                    case 0x95:\r\n                        yield return new DacStreamFastStartCommand(reader);\r\n                        break;\r\n                    case <= 0x9f:\r\n                        // Unhandled\r\n                        continue;\r\n                    case var b and 0xa0:\r\n                        yield return new AddressDataCommand(reader, b);\r\n                        break;\r\n                    case <= 0xaf:\r\n                        yield return new GenericCommand(reader, 2); // Reserved range\r\n                        break;\r\n                    case var b and <= 0xbf:\r\n                        yield return new AddressDataCommand(reader, b);\r\n                        break;\r\n                    case <= 0xdf:\r\n                        yield return new GenericCommand(reader, 3); // Reserved + some allocated\r\n                        break;\r\n                    default:\r\n                        yield return new GenericCommand(reader, 4); // Reserved + some allocated\r\n                        break;\r\n                }\r\n            }\r\n        }\r\n        public void Dispose()\r\n        {\r\n            _stream?.Dispose();\r\n            GC.SuppressFinalize(this);\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "README.md",
    "content": "# SidWizPlus\n\n![](https://github.com/maxim-zhao/SidWizPlus/wiki/logo.png)\n\nThis 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.\n\n[![](http://img.youtube.com/vi/H-Ip9c0yjGk/0.jpg)](http://www.youtube.com/watch?v=H-Ip9c0yjGk \"Sonic 3 - Ice Cap Zone - Brad Buxer\")\n[![](http://img.youtube.com/vi/ITQFs6-1LSg/0.jpg)](http://www.youtube.com/watch?v=ITQFs6-1LSg \"Bohemian Rhapsody - Queen\")\n\nThe primary goals of this project are:\n\n1. Generating videos from VGM packs from [SMS Power!](http://www.smspower.org/Music) - [see them on YouTube](https://www.youtube.com/channel/UCCsvqzh7JjNNheYTplGvhCQ)\n2. Producing a base for others to work on the features they want\n\nGet builds from here: https://github.com/maxim-zhao/SidWizPlus/releases/\n\n[![Build status](https://ci.appveyor.com/api/projects/status/vpa5eav7sm1n7ik6?svg=true)](https://ci.appveyor.com/project/maxim-zhao/sidwizplus) \n\nYou can get a compatible build of MultiDumper here: https://github.com/maxim-zhao/multidumper/releases/\n\n## Features added\n\n* Commandline mode\n* Replaced renderer with Skia, allowing simpler code and more advanced rendering:\n  * Antialiasing\n  * Background image\n  * Line width control\n  * Optional fill\n  * Alpha blending\n  * Multi-threaded rendering\n* Added rendering features from other variants\n  * Grid\n  * Channel labels\n* Integration with [MultiDumper](https://bitbucket.org/losnoco/multidumper) to generate tracks from a VGM\n  * Including automatic removal of unused tracks\n* Waveform scaling\n  * Including auto-scaling (e.g. scale peak to 100%)\n* Automatic master audio track generation, with optional ReplayGain\n* One-shot audio+video file creation via FFMPEG with optional preview\n* Or run in preview-only mode\n* Unlimited tracks and columns\n* Render to any size video\n* YouTube uploading (commandline only)\n  * Including generation of titles and descriptions from tags in VGM files\n* New GUI using the same renderer to give previews as you change settings\n  * Almost-live updates as you edit settings\n  * Preview rendering and data loading on background threads so it's pretty fast\n  * Select your preview location as you go\n  * Most parameters editable per-channel\n  * Save and load all settings\n    * Settings files are JSON so you can edit them yourself to make them \"partial\"\n  * Copy and paste channel settings\n    * Also as JSON so you can save them as text\n\n## Usage guide\n\n[Read the usage guide on the wiki](https://github.com/maxim-zhao/SidWizPlus/wiki)\n"
  },
  {
    "path": "SidWiz/ColorButton.Designer.cs",
    "content": "﻿namespace SidWizPlusGUI\r\n{\r\n    partial class ColorButton\r\n    {\r\n        /// <summary>\r\n        /// Required designer variable.\r\n        /// </summary>\r\n        private System.ComponentModel.IContainer components = null;\r\n\r\n        /// <summary> \r\n        /// Clean up any resources being used.\r\n        /// </summary>\r\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\r\n        protected override void Dispose(bool disposing)\r\n        {\r\n            if (disposing && (components != null))\r\n            {\r\n                components.Dispose();\r\n            }\r\n            base.Dispose(disposing);\r\n        }\r\n\r\n        #region Component Designer generated code\r\n\r\n        /// <summary>\r\n        /// Required method for Designer support - do not modify\r\n        /// the contents of this method with the code editor.\r\n        /// </summary>\r\n        private void InitializeComponent()\r\n        {\r\n            components = new System.ComponentModel.Container();\r\n        }\r\n\r\n        #endregion\r\n    }\r\n}\r\n"
  },
  {
    "path": "SidWiz/ColorButton.cs",
    "content": "﻿using System;\r\nusing System.ComponentModel;\r\nusing System.Drawing;\r\nusing System.Windows.Forms;\r\n\r\nnamespace SidWizPlusGUI\r\n{\r\n    /// <inheritdoc />\r\n    /// <summary>\r\n    /// Button that lets you pick a color\r\n    /// </summary>\r\n    public partial class ColorButton : Button\r\n    {\r\n        public event EventHandler ColorChanged;\r\n\r\n        private Color _color;\r\n\r\n        public Color Color\r\n        {\r\n            get => _color;\r\n            set\r\n            {\r\n                _color = value;\r\n                BackColor = value;\r\n                ForeColor = _color.GetBrightness() < 0.5 ? Color.White : Color.Black;\r\n                Text = _color.Name;\r\n                ColorChanged?.Invoke(this, EventArgs.Empty);\r\n            }\r\n        }\r\n\r\n        public ColorButton()\r\n        {\r\n            InitializeComponent();\r\n        }\r\n\r\n        public ColorButton(IContainer container)\r\n        {\r\n            container.Add(this);\r\n\r\n            InitializeComponent();\r\n\r\n            Click += OnClick;\r\n        }\r\n\r\n        private void OnClick(object sender, EventArgs e)\r\n        {\r\n            using var colorDialog = new Cyotek.Windows.Forms.ColorPickerDialog();\r\n            colorDialog.Color = _color;\r\n            colorDialog.ShowAlphaChannel = true;\r\n            if (colorDialog.ShowDialog(this) != DialogResult.OK)\r\n            {\r\n                return;\r\n            }\r\n            Color = colorDialog.Color;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "SidWiz/HighDpiHelper.cs",
    "content": "﻿using System;\r\nusing System.Collections;\r\nusing System.Drawing;\r\nusing System.Drawing.Drawing2D;\r\nusing System.Linq;\r\nusing System.Windows.Forms;\r\n\r\nnamespace SidWizPlusGUI\r\n{\r\n    public static class HighDpiHelper\r\n    {\r\n        public static void AdjustControlImagesDpiScale(Control container)\r\n        {\r\n            var dpiScale = GetDpiScale(container).Value;\r\n            if (CloseToOne(dpiScale))\r\n            {\r\n                return;\r\n            }\r\n\r\n            AdjustControlImagesDpiScale(container.Controls, dpiScale);\r\n        }\r\n\r\n        private static void AdjustControlImagesDpiScale(IEnumerable controls, float dpiScale)\r\n        {\r\n            foreach (Control control in controls)\r\n            {\r\n                switch (control)\r\n                {\r\n                    case ButtonBase { Image: not null } button:\r\n                        button.Image = ScaleImage(button.Image, dpiScale);\r\n                        break;\r\n                    case SplitContainer splitContainer:\r\n                        splitContainer.SplitterDistance = (int)(splitContainer.SplitterDistance * dpiScale);\r\n                        break;\r\n                    case TabControl { ImageList: not null } tabControl:\r\n                    {\r\n                        var imageList = new ImageList\r\n                        {\r\n                            ImageSize = ScaleSize(tabControl.ImageList.ImageSize, dpiScale),\r\n                            ColorDepth = ColorDepth.Depth32Bit\r\n                        };\r\n\r\n                        for (var i = 0 ; i < tabControl.ImageList.Images.Count; ++i)\r\n                        {\r\n                            imageList.Images.Add(\r\n                                tabControl.ImageList.Images.Keys[i], \r\n                                ScaleImage(tabControl.ImageList.Images[i], dpiScale));\r\n                        }\r\n\r\n                        tabControl.ImageList = imageList;\r\n                        break;\r\n                    }\r\n                    case ToolStrip toolStrip:\r\n                        ScaleToolStrip(dpiScale, toolStrip);\r\n\r\n                        toolStrip.AutoSize = true;\r\n                        break;\r\n                }\r\n\r\n                if (control.ContextMenuStrip != null)\r\n                {\r\n                    ScaleToolStrip(dpiScale, control.ContextMenuStrip);\r\n                }\r\n\r\n                // Then recurse\r\n                AdjustControlImagesDpiScale(control.Controls, dpiScale);\r\n            }\r\n        }\r\n\r\n        private static void ScaleToolStrip(float dpiScale, ToolStrip toolStrip)\r\n        {\r\n            toolStrip.ImageScalingSize = ScaleSize(toolStrip.ImageScalingSize, dpiScale);\r\n            foreach (var item in toolStrip.Items.Cast<ToolStripItem>().Where(i => i.Image != null))\r\n            {\r\n                item.Image = ScaleImage(item.Image, dpiScale);\r\n            }\r\n        }\r\n\r\n        private static bool CloseToOne(float dpiScale)\r\n        {\r\n            return Math.Abs(dpiScale - 1) < 0.001;\r\n        }\r\n\r\n        private static Lazy<float> GetDpiScale(Control control)\r\n        {\r\n            return new Lazy<float>(() =>\r\n            {\r\n                using var graphics = control.CreateGraphics();\r\n                return graphics.DpiX / 96.0f;\r\n            });\r\n        }\r\n\r\n        private static Image ScaleImage(Image image, float dpiScale)\r\n        {\r\n            var newSize = ScaleSize(image.Size, dpiScale);\r\n            var newBitmap = new Bitmap(newSize.Width, newSize.Height);\r\n\r\n            using (var g = Graphics.FromImage(newBitmap))\r\n            {\r\n                g.PixelOffsetMode = PixelOffsetMode.HighQuality;\r\n                g.InterpolationMode = InterpolationMode.NearestNeighbor;\r\n                g.DrawImage(image, new Rectangle(new Point(), newSize));\r\n            }\r\n\r\n            image.Dispose();\r\n\r\n            return newBitmap;\r\n        }\r\n\r\n        private static Size ScaleSize(Size size, float scale)\r\n        {\r\n            return new Size((int) (size.Width * scale), (int) (size.Height * scale));\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "SidWiz/MultiDumperForm.Designer.cs",
    "content": "﻿using System.ComponentModel;\r\nusing System.Windows.Forms;\r\n\r\nnamespace SidWizPlusGUI\r\n{\r\n    partial class MultiDumperForm\r\n    {\r\n        /// <summary>\r\n        /// Required designer variable.\r\n        /// </summary>\r\n        private System.ComponentModel.IContainer components = null;\r\n\r\n        /// <summary>\r\n        /// Clean up any resources being used.\r\n        /// </summary>\r\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\r\n        protected override void Dispose(bool disposing)\r\n        {\r\n            if (disposing && (components != null))\r\n            {\r\n                components.Dispose();\r\n            }\r\n            base.Dispose(disposing);\r\n        }\r\n\r\n        #region Windows Form Designer generated code\r\n\r\n        /// <summary>\r\n        /// Required method for Designer support - do not modify\r\n        /// the contents of this method with the code editor.\r\n        /// </summary>\r\n        private void InitializeComponent()\r\n        {\r\n            this.Subsongs = new System.Windows.Forms.ListBox();\r\n            this.OKButton = new System.Windows.Forms.Button();\r\n            this.ProgressBar = new System.Windows.Forms.ProgressBar();\r\n            this.label1 = new System.Windows.Forms.Label();\r\n            this.lengthBox = new System.Windows.Forms.TextBox();\r\n            this.SuspendLayout();\r\n            // \r\n            // Subsongs\r\n            // \r\n            this.Subsongs.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \r\n            | System.Windows.Forms.AnchorStyles.Left) \r\n            | System.Windows.Forms.AnchorStyles.Right)));\r\n            this.Subsongs.FormattingEnabled = true;\r\n            this.Subsongs.IntegralHeight = false;\r\n            this.Subsongs.Items.AddRange(new object[] {\r\n            \" \"});\r\n            this.Subsongs.Location = new System.Drawing.Point(12, 12);\r\n            this.Subsongs.Name = \"Subsongs\";\r\n            this.Subsongs.Size = new System.Drawing.Size(663, 237);\r\n            this.Subsongs.TabIndex = 0;\r\n            this.Subsongs.SelectedIndexChanged += new System.EventHandler(this.Subsongs_SelectedIndexChanged);\r\n            this.Subsongs.DoubleClick += new System.EventHandler(this.OkButtonClick);\r\n            // \r\n            // OKButton\r\n            // \r\n            this.OKButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));\r\n            this.OKButton.Enabled = false;\r\n            this.OKButton.Location = new System.Drawing.Point(600, 291);\r\n            this.OKButton.Name = \"OKButton\";\r\n            this.OKButton.Size = new System.Drawing.Size(75, 23);\r\n            this.OKButton.TabIndex = 4;\r\n            this.OKButton.Text = \"OK\";\r\n            this.OKButton.UseVisualStyleBackColor = true;\r\n            this.OKButton.Click += new System.EventHandler(this.OkButtonClick);\r\n            // \r\n            // ProgressBar\r\n            // \r\n            this.ProgressBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) \r\n            | System.Windows.Forms.AnchorStyles.Right)));\r\n            this.ProgressBar.Location = new System.Drawing.Point(12, 291);\r\n            this.ProgressBar.Name = \"ProgressBar\";\r\n            this.ProgressBar.Size = new System.Drawing.Size(582, 23);\r\n            this.ProgressBar.Style = System.Windows.Forms.ProgressBarStyle.Continuous;\r\n            this.ProgressBar.TabIndex = 3;\r\n            // \r\n            // label1\r\n            // \r\n            this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));\r\n            this.label1.AutoSize = true;\r\n            this.label1.Enabled = false;\r\n            this.label1.Location = new System.Drawing.Point(9, 263);\r\n            this.label1.Name = \"label1\";\r\n            this.label1.Size = new System.Drawing.Size(102, 13);\r\n            this.label1.TabIndex = 1;\r\n            this.label1.Text = \"Song length (mm:ss)\";\r\n            // \r\n            // lengthBox\r\n            // \r\n            this.lengthBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));\r\n            this.lengthBox.Enabled = false;\r\n            this.lengthBox.Location = new System.Drawing.Point(117, 260);\r\n            this.lengthBox.Name = \"lengthBox\";\r\n            this.lengthBox.Size = new System.Drawing.Size(100, 20);\r\n            this.lengthBox.TabIndex = 2;\r\n            // \r\n            // MultiDumperForm\r\n            // \r\n            this.AcceptButton = this.OKButton;\r\n            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);\r\n            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;\r\n            this.ClientSize = new System.Drawing.Size(687, 327);\r\n            this.Controls.Add(this.lengthBox);\r\n            this.Controls.Add(this.label1);\r\n            this.Controls.Add(this.ProgressBar);\r\n            this.Controls.Add(this.OKButton);\r\n            this.Controls.Add(this.Subsongs);\r\n            this.MaximizeBox = false;\r\n            this.MinimizeBox = false;\r\n            this.Name = \"MultiDumperForm\";\r\n            this.ShowIcon = false;\r\n            this.ShowInTaskbar = false;\r\n            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;\r\n            this.Text = \"Multidumper subsong selection\";\r\n            this.Closing += new System.ComponentModel.CancelEventHandler(this.SubsongSelectionForm_Closing);\r\n            this.Load += new System.EventHandler(this.SubSongSelectionForm_Load);\r\n            this.ResumeLayout(false);\r\n            this.PerformLayout();\r\n\r\n        }\r\n\r\n        #endregion\r\n\r\n        private System.Windows.Forms.ListBox Subsongs;\r\n        private System.Windows.Forms.Button OKButton;\r\n        private System.Windows.Forms.ProgressBar ProgressBar;\r\n        private Label label1;\r\n        private TextBox lengthBox;\r\n    }\r\n}"
  },
  {
    "path": "SidWiz/MultiDumperForm.cs",
    "content": "﻿using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Threading.Tasks;\r\nusing System.Windows.Forms;\r\nusing LibSidWiz;\r\n\r\nnamespace SidWizPlusGUI\r\n{\r\n    public partial class MultiDumperForm : Form\r\n    {\r\n        private readonly string _filename;\r\n        private readonly MultiDumperWrapper _wrapper;\r\n\r\n        public IEnumerable<string> Filenames { get; private set; }\r\n\r\n        public MultiDumperForm(string filename, string multiDumperPath, int samplingRate, int loopCount, int fadeMs, int gapMs, string extraOptions)\r\n        {\r\n            _filename = filename;\r\n            _wrapper = new MultiDumperWrapper(multiDumperPath, samplingRate, loopCount, fadeMs, gapMs, extraOptions);\r\n            InitializeComponent();\r\n        }\r\n\r\n        private void OkButtonClick(object sender, EventArgs e)\r\n        {\r\n            if (!(Subsongs.SelectedItem is MultiDumperWrapper.Song song))\r\n            {\r\n                return;\r\n            }\r\n\r\n            if (song.GetLength() <= TimeSpan.Zero)\r\n            {\r\n                // Try to parse the text box\r\n                if (!TimeSpan.TryParseExact(lengthBox.Text, \"m\\\\:ss\", null, out var length))\r\n                {\r\n                    return;\r\n                }\r\n\r\n                song.ForceLength = length;\r\n            }\r\n\r\n            OKButton.Enabled = false;\r\n\r\n            // We start a task to wrap the load task\r\n            Task.Factory.StartNew(() =>\r\n            {\r\n                try\r\n                {\r\n                    Filenames = _wrapper.Dump(song,\r\n                        progress =>\r\n                        {\r\n                            ProgressBar.BeginInvoke(\r\n                                new Action(() => ProgressBar.Value = (int) (progress * 100)));\r\n                        }).ToList();\r\n\r\n                    BeginInvoke(new Action(() =>\r\n                    {\r\n                        DialogResult = DialogResult.OK;\r\n                        Close();\r\n                    }));\r\n                }\r\n                catch (Exception)\r\n                {\r\n                    BeginInvoke(new Action(() =>\r\n                    {\r\n                        DialogResult = DialogResult.Cancel;\r\n                        Filenames = null;\r\n                        Close();\r\n                    }));\r\n                }\r\n            });\r\n        }\r\n\r\n        private void SubSongSelectionForm_Load(object sender, EventArgs e)\r\n        {\r\n            Subsongs.Items.Clear();\r\n            Subsongs.Items.Add($\"Checking {_filename}...\");\r\n            // Start a task to load the metadata\r\n            Task.Factory.StartNew(() =>\r\n            {\r\n                try\r\n                {\r\n                    var songs = _wrapper.GetSongs(_filename).ToList();\r\n                    Subsongs.BeginInvoke(new Action(() =>\r\n                    {\r\n                        // Back on the GUI thread...\r\n                        Subsongs.Items.Clear();\r\n                        Subsongs.Items.AddRange(songs.ToArray<object>());\r\n\r\n                        Subsongs.SelectedIndex = 0;\r\n                        OKButton.Enabled = true;\r\n\r\n                        if (songs.Count == 1 && songs[0].GetLength() > TimeSpan.Zero)\r\n                        {\r\n                            // If only one song, and it has a length, choose it\r\n                            OkButtonClick(this, EventArgs.Empty);\r\n                        }\r\n                    }));\r\n                }\r\n                catch (Exception ex)\r\n                {\r\n                    Subsongs.BeginInvoke(new Action(() =>\r\n                    {\r\n                        // Back on the GUI thread...\r\n                        Subsongs.Items.Add($\"Failed to read {_filename}: {ex.Message}\");\r\n                    }));\r\n                }\r\n            });\r\n        }\r\n\r\n        private void SubsongSelectionForm_Closing(object sender, EventArgs e)\r\n        {\r\n            _wrapper.Dispose();\r\n        }\r\n\r\n        private void Subsongs_SelectedIndexChanged(object sender, EventArgs e)\r\n        {\r\n            // We enable the length controls if the track is missing info\r\n            label1.Enabled = lengthBox.Enabled = Subsongs.SelectedItem is MultiDumperWrapper.Song s && s.GetLength() <= TimeSpan.Zero;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "SidWiz/MultiDumperForm.resx",
    "content": "﻿<?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    The primary goals of this format is to allow a simple XML format \r\n    that is mostly human readable. The generation and parsing of the \r\n    various data types are done through the TypeConverter classes \r\n    associated with the data types.\r\n    \r\n    Example:\r\n    \r\n    ... ado.net/XML headers & schema ...\r\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\r\n    <resheader name=\"version\">2.0</resheader>\r\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\r\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\r\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\r\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\r\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\r\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\r\n    </data>\r\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\r\n        <comment>This is a comment</comment>\r\n    </data>\r\n                \r\n    There are any number of \"resheader\" rows that contain simple \r\n    name/value pairs.\r\n    \r\n    Each data row contains a name, and value. The row also contains a \r\n    type or mimetype. Type corresponds to a .NET class that support \r\n    text/value conversion through the TypeConverter architecture. \r\n    Classes that don't support this are serialized and stored with the \r\n    mimetype set.\r\n    \r\n    The mimetype is used for serialized objects, and tells the \r\n    ResXResourceReader how to depersist the object. This is currently not \r\n    extensible. For a given mimetype the value must be set accordingly:\r\n    \r\n    Note - application/x-microsoft.net.object.binary.base64 is the format \r\n    that the ResXResourceWriter will generate, however the reader can \r\n    read any of the formats listed below.\r\n    \r\n    mimetype: application/x-microsoft.net.object.binary.base64\r\n    value   : The object must be serialized with \r\n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\r\n            : and then encoded with base64 encoding.\r\n    \r\n    mimetype: application/x-microsoft.net.object.soap.base64\r\n    value   : The object must be serialized with \r\n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\r\n            : and then encoded with base64 encoding.\r\n\r\n    mimetype: application/x-microsoft.net.object.bytearray.base64\r\n    value   : The object must be serialized into a byte array \r\n            : using a System.ComponentModel.TypeConverter\r\n            : and then encoded with base64 encoding.\r\n    -->\r\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\r\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\r\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\r\n      <xsd:complexType>\r\n        <xsd:choice maxOccurs=\"unbounded\">\r\n          <xsd:element name=\"metadata\">\r\n            <xsd:complexType>\r\n              <xsd:sequence>\r\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\r\n              </xsd:sequence>\r\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\r\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\r\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\r\n              <xsd:attribute ref=\"xml:space\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n          <xsd:element name=\"assembly\">\r\n            <xsd:complexType>\r\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\r\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n          <xsd:element name=\"data\">\r\n            <xsd:complexType>\r\n              <xsd:sequence>\r\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\r\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\r\n              </xsd:sequence>\r\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\r\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\r\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\r\n              <xsd:attribute ref=\"xml:space\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n          <xsd:element name=\"resheader\">\r\n            <xsd:complexType>\r\n              <xsd:sequence>\r\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\r\n              </xsd:sequence>\r\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n        </xsd:choice>\r\n      </xsd:complexType>\r\n    </xsd:element>\r\n  </xsd:schema>\r\n  <resheader name=\"resmimetype\">\r\n    <value>text/microsoft-resx</value>\r\n  </resheader>\r\n  <resheader name=\"version\">\r\n    <value>2.0</value>\r\n  </resheader>\r\n  <resheader name=\"reader\">\r\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\r\n  </resheader>\r\n  <resheader name=\"writer\">\r\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\r\n  </resheader>\r\n</root>"
  },
  {
    "path": "SidWiz/Program.cs",
    "content": "﻿using System;\nusing System.Windows.Forms;\n\nnamespace SidWizPlusGUI\n{\n    internal static class Program\n    {\n        /// <summary>\n        /// The main entry point for the application.\n        /// </summary>\n        [STAThread]\n        private static void Main()\n        {\n            Application.EnableVisualStyles();\n            Application.SetCompatibleTextRenderingDefault(false);\n            Application.Run(new SidWizPlusGui());\n        }\n    }\n}\n"
  },
  {
    "path": "SidWiz/Properties/Resources.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\r\n// <auto-generated>\r\n//     This code was generated by a tool.\r\n//     Runtime Version:4.0.30319.42000\r\n//\r\n//     Changes to this file may cause incorrect behavior and will be lost if\r\n//     the code is regenerated.\r\n// </auto-generated>\r\n//------------------------------------------------------------------------------\r\n\r\nnamespace SidWizPlusGUI.Properties {\r\n    using System;\r\n    \r\n    \r\n    /// <summary>\r\n    ///   A strongly-typed resource class, for looking up localized strings, etc.\r\n    /// </summary>\r\n    // This class was auto-generated by the StronglyTypedResourceBuilder\r\n    // class via a tool like ResGen or Visual Studio.\r\n    // To add or remove a member, edit your .ResX file then rerun ResGen\r\n    // with the /str option, or rebuild your VS project.\r\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"System.Resources.Tools.StronglyTypedResourceBuilder\", \"17.0.0.0\")]\r\n    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\r\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\r\n    internal class Resources {\r\n        \r\n        private static global::System.Resources.ResourceManager resourceMan;\r\n        \r\n        private static global::System.Globalization.CultureInfo resourceCulture;\r\n        \r\n        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(\"Microsoft.Performance\", \"CA1811:AvoidUncalledPrivateCode\")]\r\n        internal Resources() {\r\n        }\r\n        \r\n        /// <summary>\r\n        ///   Returns the cached ResourceManager instance used by this class.\r\n        /// </summary>\r\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\r\n        internal static global::System.Resources.ResourceManager ResourceManager {\r\n            get {\r\n                if (object.ReferenceEquals(resourceMan, null)) {\r\n                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(\"SidWizPlusGUI.Properties.Resources\", typeof(Resources).Assembly);\r\n                    resourceMan = temp;\r\n                }\r\n                return resourceMan;\r\n            }\r\n        }\r\n        \r\n        /// <summary>\r\n        ///   Overrides the current thread's CurrentUICulture property for all\r\n        ///   resource lookups using this strongly typed resource class.\r\n        /// </summary>\r\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\r\n        internal static global::System.Globalization.CultureInfo Culture {\r\n            get {\r\n                return resourceCulture;\r\n            }\r\n            set {\r\n                resourceCulture = value;\r\n            }\r\n        }\r\n        \r\n        /// <summary>\r\n        ///   Looks up a localized resource of type System.Drawing.Bitmap.\r\n        /// </summary>\r\n        internal static System.Drawing.Bitmap lightning {\r\n            get {\r\n                object obj = ResourceManager.GetObject(\"lightning\", resourceCulture);\r\n                return ((System.Drawing.Bitmap)(obj));\r\n            }\r\n        }\r\n        \r\n        /// <summary>\r\n        ///   Looks up a localized resource of type System.Drawing.Bitmap.\r\n        /// </summary>\r\n        internal static System.Drawing.Bitmap page {\r\n            get {\r\n                object obj = ResourceManager.GetObject(\"page\", resourceCulture);\r\n                return ((System.Drawing.Bitmap)(obj));\r\n            }\r\n        }\r\n        \r\n        /// <summary>\r\n        ///   Looks up a localized resource of type System.Drawing.Bitmap.\r\n        /// </summary>\r\n        internal static System.Drawing.Bitmap page_refresh {\r\n            get {\r\n                object obj = ResourceManager.GetObject(\"page_refresh\", resourceCulture);\r\n                return ((System.Drawing.Bitmap)(obj));\r\n            }\r\n        }\r\n        \r\n        /// <summary>\r\n        ///   Looks up a localized resource of type System.Drawing.Bitmap.\r\n        /// </summary>\r\n        internal static System.Drawing.Bitmap page_save {\r\n            get {\r\n                object obj = ResourceManager.GetObject(\"page_save\", resourceCulture);\r\n                return ((System.Drawing.Bitmap)(obj));\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "SidWiz/Properties/Resources.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <assembly alias=\"System.Windows.Forms\" name=\"System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" />\n  <data name=\"page_refresh\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\page_refresh.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"page_save\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\page_save.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"lightning\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\lightning.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"page\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\page.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n</root>"
  },
  {
    "path": "SidWiz/Properties/app.manifest",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\">\r\n  <assemblyIdentity version=\"1.0.0.0\" name=\"MyApplication.app\"/>\r\n  <trustInfo xmlns=\"urn:schemas-microsoft-com:asm.v2\">\r\n    <security>\r\n      <requestedPrivileges xmlns=\"urn:schemas-microsoft-com:asm.v3\">\r\n        <!-- UAC Manifest Options\r\n             If you want to change the Windows User Account Control level replace the \r\n             requestedExecutionLevel node with one of the following.\r\n\r\n        <requestedExecutionLevel  level=\"asInvoker\" uiAccess=\"false\" />\r\n        <requestedExecutionLevel  level=\"requireAdministrator\" uiAccess=\"false\" />\r\n        <requestedExecutionLevel  level=\"highestAvailable\" uiAccess=\"false\" />\r\n\r\n            Specifying requestedExecutionLevel element will disable file and registry virtualization. \r\n            Remove this element if your application requires this virtualization for backwards\r\n            compatibility.\r\n        -->\r\n        <requestedExecutionLevel level=\"asInvoker\" uiAccess=\"false\" />\r\n      </requestedPrivileges>\r\n    </security>\r\n  </trustInfo>\r\n\r\n  <compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\r\n    <application>\r\n      <!-- A list of the Windows versions that this application has been tested on\r\n           and is designed to work with. Uncomment the appropriate elements\r\n           and Windows will automatically select the most compatible environment. -->\r\n\r\n      <!-- Windows Vista -->\r\n      <supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\" />\r\n\r\n      <!-- Windows 7 -->\r\n      <supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\" />\r\n\r\n      <!-- Windows 8 -->\r\n      <supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\" />\r\n\r\n      <!-- Windows 8.1 -->\r\n      <supportedOS Id=\"{1f676c76-80e1-4239-95bb-83d0f6d0da78}\" />\r\n\r\n      <!-- Windows 10 -->\r\n      <supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\" />\r\n\r\n    </application>\r\n  </compatibility>\r\n\r\n  <!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher\r\n       DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need \r\n       to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should \r\n       also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->\r\n  <application xmlns=\"urn:schemas-microsoft-com:asm.v3\">\r\n    <windowsSettings>\r\n      <dpiAware xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">true/PM</dpiAware>\r\n    </windowsSettings>\r\n  </application>\r\n  <!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->\r\n  <dependency>\r\n    <dependentAssembly>\r\n      <assemblyIdentity\r\n          type=\"win32\"\r\n          name=\"Microsoft.Windows.Common-Controls\"\r\n          version=\"6.0.0.0\"\r\n          processorArchitecture=\"*\"\r\n          publicKeyToken=\"6595b64144ccf1df\"\r\n          language=\"*\"\r\n        />\r\n    </dependentAssembly>\r\n  </dependency>\r\n\r\n</assembly>\r\n"
  },
  {
    "path": "SidWiz/SidPlayForm.Designer.cs",
    "content": "﻿using System.Windows.Forms;\r\n\r\nnamespace SidWizPlusGUI\r\n{\r\n    partial class SidPlayForm\r\n    {\r\n        /// <summary>\r\n        /// Required designer variable.\r\n        /// </summary>\r\n        private System.ComponentModel.IContainer components = null;\r\n\r\n        /// <summary>\r\n        /// Clean up any resources being used.\r\n        /// </summary>\r\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\r\n        protected override void Dispose(bool disposing)\r\n        {\r\n            if (disposing && (components != null))\r\n            {\r\n                components.Dispose();\r\n            }\r\n            base.Dispose(disposing);\r\n        }\r\n\r\n        #region Windows Form Designer generated code\r\n\r\n        /// <summary>\r\n        /// Required method for Designer support - do not modify\r\n        /// the contents of this method with the code editor.\r\n        /// </summary>\r\n        private void InitializeComponent()\r\n        {\r\n            this.Subsongs = new System.Windows.Forms.ListBox();\r\n            this.OKButton = new System.Windows.Forms.Button();\r\n            this.ProgressBar = new System.Windows.Forms.ProgressBar();\r\n            this.SuspendLayout();\r\n            // \r\n            // Subsongs\r\n            // \r\n            this.Subsongs.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \r\n            | System.Windows.Forms.AnchorStyles.Left) \r\n            | System.Windows.Forms.AnchorStyles.Right)));\r\n            this.Subsongs.FormattingEnabled = true;\r\n            this.Subsongs.IntegralHeight = false;\r\n            this.Subsongs.ItemHeight = 25;\r\n            this.Subsongs.Items.AddRange(new object[] {\r\n            \" \"});\r\n            this.Subsongs.Location = new System.Drawing.Point(15, 15);\r\n            this.Subsongs.Margin = new System.Windows.Forms.Padding(6);\r\n            this.Subsongs.Name = \"Subsongs\";\r\n            this.Subsongs.Size = new System.Drawing.Size(998, 364);\r\n            this.Subsongs.TabIndex = 0;\r\n            this.Subsongs.DoubleClick += new System.EventHandler(this.OkButtonClick);\r\n            // \r\n            // OKButton\r\n            // \r\n            this.OKButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));\r\n            this.OKButton.Enabled = false;\r\n            this.OKButton.Location = new System.Drawing.Point(863, 391);\r\n            this.OKButton.Margin = new System.Windows.Forms.Padding(6);\r\n            this.OKButton.Name = \"OKButton\";\r\n            this.OKButton.Size = new System.Drawing.Size(150, 44);\r\n            this.OKButton.TabIndex = 2;\r\n            this.OKButton.Text = \"OK\";\r\n            this.OKButton.UseVisualStyleBackColor = true;\r\n            this.OKButton.Click += new System.EventHandler(this.OkButtonClick);\r\n            // \r\n            // ProgressBar\r\n            // \r\n            this.ProgressBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) \r\n            | System.Windows.Forms.AnchorStyles.Right)));\r\n            this.ProgressBar.Location = new System.Drawing.Point(15, 391);\r\n            this.ProgressBar.Margin = new System.Windows.Forms.Padding(6);\r\n            this.ProgressBar.Name = \"ProgressBar\";\r\n            this.ProgressBar.Size = new System.Drawing.Size(836, 44);\r\n            this.ProgressBar.Style = System.Windows.Forms.ProgressBarStyle.Continuous;\r\n            this.ProgressBar.TabIndex = 4;\r\n            // \r\n            // SidPlayForm\r\n            // \r\n            this.AcceptButton = this.OKButton;\r\n            this.AutoScaleDimensions = new System.Drawing.SizeF(12F, 25F);\r\n            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;\r\n            this.ClientSize = new System.Drawing.Size(1028, 450);\r\n            this.Controls.Add(this.ProgressBar);\r\n            this.Controls.Add(this.OKButton);\r\n            this.Controls.Add(this.Subsongs);\r\n            this.Margin = new System.Windows.Forms.Padding(6);\r\n            this.MaximizeBox = false;\r\n            this.MinimizeBox = false;\r\n            this.Name = \"SidPlayForm\";\r\n            this.ShowIcon = false;\r\n            this.ShowInTaskbar = false;\r\n            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;\r\n            this.Text = \"SidPlay subsong selection\";\r\n            this.Closing += new System.ComponentModel.CancelEventHandler(this.SubSongSelectionForm_Closing);\r\n            this.Load += new System.EventHandler(this.SubSongSelectionForm_Load);\r\n            this.ResumeLayout(false);\r\n\r\n        }\r\n\r\n        #endregion\r\n\r\n        private System.Windows.Forms.ListBox Subsongs;\r\n        private System.Windows.Forms.Button OKButton;\r\n        private ProgressBar ProgressBar;\r\n    }\r\n}"
  },
  {
    "path": "SidWiz/SidPlayForm.cs",
    "content": "﻿using System;\r\nusing System.Collections.Generic;\r\nusing System.IO;\r\nusing System.Linq;\r\nusing System.Text;\r\nusing System.Threading.Tasks;\r\nusing System.Windows.Forms;\r\nusing LibSidWiz;\r\n\r\nnamespace SidWizPlusGUI\r\n{\r\n    public partial class SidPlayForm : Form\r\n    {\r\n        public class SidFile\r\n        {\r\n            public string Filename { get; }\r\n\r\n            public string Released { get; }\r\n\r\n            public string Name { get; }\r\n\r\n            public string Author { get; }\r\n\r\n            public int SongCount { get; }\r\n\r\n            public string Chip { get; }\r\n\r\n            // ReSharper disable once MemberCanBePrivate.Global\r\n            public int Version { get; }\r\n\r\n            public SidFile(string filename)\r\n            {\r\n                Filename = filename;\r\n                using var f = new FileStream(filename, FileMode.Open);\r\n                using var r = new BinaryReader(f, Encoding.ASCII);\r\n                // C64 is big-endian...\r\n                f.Seek(0x04, SeekOrigin.Begin);\r\n                Version = r.ReadByte() << 8 | r.ReadByte();\r\n                f.Seek(0x0e, SeekOrigin.Begin);\r\n                SongCount = r.ReadByte() << 8 | r.ReadByte();\r\n                f.Seek(0x16, SeekOrigin.Begin);\r\n                Name = new string(r.ReadChars(32)).TrimEnd('\\0');\r\n                Author = new string(r.ReadChars(32)).TrimEnd('\\0');\r\n                Released = new string(r.ReadChars(32)).TrimEnd('\\0');\r\n\r\n                if (Version >= 2)\r\n                {\r\n                    f.Seek(0x76, SeekOrigin.Begin);\r\n                    var flags = r.ReadByte() << 8 | r.ReadByte();\r\n                    switch ((flags >> 4) & 0b11)\r\n                    {\r\n                        case 0b00:\r\n                            Chip = \"SID\";\r\n                            break;\r\n                        case 0b01:\r\n                            Chip = \"MOS6581\";\r\n                            break;\r\n                        case 0b10:\r\n                            Chip = \"MOS8580\";\r\n                            break;\r\n                        case 0b11:\r\n                            Chip = \"MOS6581 & MOS8580\";\r\n                            break;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        public class SidPlayWrapper : IDisposable\r\n        {\r\n            private readonly string _sidPlayPath;\r\n            private IList<ProcessWrapper> _processWrappers;\r\n\r\n            public SidPlayWrapper(string sidPlayPath)\r\n            {\r\n                _sidPlayPath = sidPlayPath;\r\n            }\r\n\r\n            public class Song\r\n            {\r\n                public SidFile File { get; set; }\r\n                public int Index { get; set; }\r\n\r\n                public override string ToString()\r\n                {\r\n                    return $\"#{Index}: {File.Name} - {File.Author} - {File.Released}\";\r\n                }\r\n            }\r\n\r\n            public IEnumerable<Song> GetSongs(string filename)\r\n            {\r\n                filename = Path.GetFullPath(filename);\r\n\r\n                if (!File.Exists(filename))\r\n                {\r\n                    throw new FileNotFoundException(\"Cannot find file\", filename);\r\n                }\r\n\r\n                var file = new SidFile(filename);\r\n                return Enumerable.Range(1, file.SongCount).Select(index => new Song {File = file, Index = index});\r\n            }\r\n\r\n            public IEnumerable<string> Dump(Song song)\r\n            {\r\n                var filenames = new List<string>();\r\n\r\n                _processWrappers = Enumerable.Range(1, 4).Select(channel =>\r\n                {\r\n                    var muting = string.Join(\" \",\r\n                        Enumerable.Range(1, 4).Where(n => n != channel).Select(n => $\"-u{n}\"));\r\n                    var filename = $\"{song.File.Filename} - Song {song.Index} - {song.File.Chip} #{channel}.wav\";\r\n                    filenames.Add(filename);\r\n                    return new ProcessWrapper(\r\n                        _sidPlayPath,\r\n                        $\"{muting} -os -o{song.Index} -w\\\"{filename}\\\" \\\"{song.File.Filename}\\\"\");\r\n                }).ToList();\r\n                    \r\n                // sidplayfp doesn't emit anything to stdout... so we just block until they all exit\r\n                foreach (var wrapper in _processWrappers)\r\n                {\r\n                    wrapper.WaitForExit();\r\n                    wrapper.Dispose();\r\n                }\r\n\r\n                _processWrappers.Clear();\r\n\r\n                // Older sidplayfp versions use the filename we give; newer ones always add .wav to the end.\r\n                // We check which one appeared.\r\n                return filenames.Select(x => File.Exists(x + \".wav\")\r\n                    ? x + \".wav\"\r\n                    : x);\r\n            }\r\n\r\n            public void Dispose()\r\n            {\r\n                foreach (var wrapper in _processWrappers)\r\n                {\r\n                    wrapper.Dispose();\r\n                }\r\n            }\r\n        }\r\n\r\n\r\n        private readonly string _filename;\r\n        private readonly SidPlayWrapper _wrapper;\r\n\r\n        public IEnumerable<string> Filenames { get; private set; }\r\n\r\n        public SidPlayForm(string filename, string sidPlayPath)\r\n        {\r\n            _filename = filename;\r\n            _wrapper = new SidPlayWrapper(sidPlayPath);\r\n            InitializeComponent();\r\n        }\r\n\r\n        private void OkButtonClick(object sender, EventArgs e)\r\n        {\r\n            OKButton.Enabled = false;\r\n\r\n            if (Subsongs.SelectedItem is SidPlayWrapper.Song song)\r\n            {\r\n                ProgressBar.Style = ProgressBarStyle.Marquee;\r\n\r\n                // We start a task to wrap the load task\r\n                Task.Factory.StartNew(() =>\r\n                {\r\n                    try\r\n                    {\r\n                        Filenames = _wrapper.Dump(song).ToList();\r\n\r\n                        BeginInvoke(new Action(() =>\r\n                        {\r\n                            DialogResult = DialogResult.OK;\r\n                            Close();\r\n                        }));\r\n                    }\r\n                    catch (Exception)\r\n                    {\r\n                        BeginInvoke(new Action(() =>\r\n                        {\r\n                            DialogResult = DialogResult.Cancel;\r\n                            Filenames = null;\r\n                            Close();\r\n                        }));\r\n                    }\r\n                });\r\n            }\r\n        }\r\n\r\n        private void SubSongSelectionForm_Load(object sender, EventArgs e)\r\n        {\r\n            Subsongs.Items.Clear();\r\n            Subsongs.Items.Add($\"Checking {_filename}...\");\r\n            // Start a task to load the metadata\r\n            Task.Factory.StartNew(() =>\r\n            {\r\n                var songs = _wrapper.GetSongs(_filename);\r\n                Subsongs.BeginInvoke(new Action(() =>\r\n                {\r\n                    // Back on the GUI thread...\r\n                    Subsongs.Items.Clear();\r\n                    Subsongs.Items.AddRange(songs.ToArray<object>());\r\n\r\n                    Subsongs.SelectedIndex = 0;\r\n                    OKButton.Enabled = true;\r\n\r\n                    if (Subsongs.Items.Count == 1)\r\n                    {\r\n                        // If only one song, select it\r\n                        OkButtonClick(this, EventArgs.Empty);\r\n                    }\r\n                }));\r\n            });\r\n        }\r\n\r\n        private void SubSongSelectionForm_Closing(object sender, EventArgs e)\r\n        {\r\n            _wrapper.Dispose();\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "SidWiz/SidPlayForm.resx",
    "content": "﻿<?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    The primary goals of this format is to allow a simple XML format \r\n    that is mostly human readable. The generation and parsing of the \r\n    various data types are done through the TypeConverter classes \r\n    associated with the data types.\r\n    \r\n    Example:\r\n    \r\n    ... ado.net/XML headers & schema ...\r\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\r\n    <resheader name=\"version\">2.0</resheader>\r\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\r\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\r\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\r\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\r\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\r\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\r\n    </data>\r\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\r\n        <comment>This is a comment</comment>\r\n    </data>\r\n                \r\n    There are any number of \"resheader\" rows that contain simple \r\n    name/value pairs.\r\n    \r\n    Each data row contains a name, and value. The row also contains a \r\n    type or mimetype. Type corresponds to a .NET class that support \r\n    text/value conversion through the TypeConverter architecture. \r\n    Classes that don't support this are serialized and stored with the \r\n    mimetype set.\r\n    \r\n    The mimetype is used for serialized objects, and tells the \r\n    ResXResourceReader how to depersist the object. This is currently not \r\n    extensible. For a given mimetype the value must be set accordingly:\r\n    \r\n    Note - application/x-microsoft.net.object.binary.base64 is the format \r\n    that the ResXResourceWriter will generate, however the reader can \r\n    read any of the formats listed below.\r\n    \r\n    mimetype: application/x-microsoft.net.object.binary.base64\r\n    value   : The object must be serialized with \r\n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\r\n            : and then encoded with base64 encoding.\r\n    \r\n    mimetype: application/x-microsoft.net.object.soap.base64\r\n    value   : The object must be serialized with \r\n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\r\n            : and then encoded with base64 encoding.\r\n\r\n    mimetype: application/x-microsoft.net.object.bytearray.base64\r\n    value   : The object must be serialized into a byte array \r\n            : using a System.ComponentModel.TypeConverter\r\n            : and then encoded with base64 encoding.\r\n    -->\r\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\r\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\r\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\r\n      <xsd:complexType>\r\n        <xsd:choice maxOccurs=\"unbounded\">\r\n          <xsd:element name=\"metadata\">\r\n            <xsd:complexType>\r\n              <xsd:sequence>\r\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\r\n              </xsd:sequence>\r\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\r\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\r\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\r\n              <xsd:attribute ref=\"xml:space\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n          <xsd:element name=\"assembly\">\r\n            <xsd:complexType>\r\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\r\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n          <xsd:element name=\"data\">\r\n            <xsd:complexType>\r\n              <xsd:sequence>\r\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\r\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\r\n              </xsd:sequence>\r\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\r\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\r\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\r\n              <xsd:attribute ref=\"xml:space\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n          <xsd:element name=\"resheader\">\r\n            <xsd:complexType>\r\n              <xsd:sequence>\r\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\r\n              </xsd:sequence>\r\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n        </xsd:choice>\r\n      </xsd:complexType>\r\n    </xsd:element>\r\n  </xsd:schema>\r\n  <resheader name=\"resmimetype\">\r\n    <value>text/microsoft-resx</value>\r\n  </resheader>\r\n  <resheader name=\"version\">\r\n    <value>2.0</value>\r\n  </resheader>\r\n  <resheader name=\"reader\">\r\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\r\n  </resheader>\r\n  <resheader name=\"writer\">\r\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\r\n  </resheader>\r\n</root>"
  },
  {
    "path": "SidWiz/SidWizPlusGUI.Designer.cs",
    "content": "﻿using System.Windows.Forms;\r\n\r\nnamespace SidWizPlusGUI\r\n{\r\n    partial class SidWizPlusGui\r\n    {\r\n        /// <summary>\r\n        /// Required designer variable.\r\n        /// </summary>\r\n        private System.ComponentModel.IContainer components = null;\r\n\r\n        /// <summary>\r\n        /// Clean up any resources being used.\r\n        /// </summary>\r\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\r\n        protected override void Dispose(bool disposing)\r\n        {\r\n            if (disposing && (components != null))\r\n            {\r\n                components.Dispose();\r\n            }\r\n            base.Dispose(disposing);\r\n        }\r\n\r\n        #region Windows Form Designer generated code\r\n\r\n        /// <summary>\r\n        /// Required method for Designer support - do not modify\r\n        /// the contents of this method with the code editor.\r\n        /// </summary>\r\n        private void InitializeComponent()\r\n        {\r\n            this.components = new System.ComponentModel.Container();\r\n            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SidWizPlusGui));\r\n            this.splitContainer1 = new System.Windows.Forms.SplitContainer();\r\n            this.Preview = new System.Windows.Forms.PictureBox();\r\n            this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components);\r\n            this.moveLeftupToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();\r\n            this.moveRightdownToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();\r\n            this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator();\r\n            this.cloneChannelToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();\r\n            this.removeChannelToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();\r\n            this.splitChannelToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();\r\n            this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator();\r\n            this.copySettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();\r\n            this.pasteSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();\r\n            this.applySettingsToOtherChannelsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();\r\n            this.setAsdefaultsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();\r\n            this.resetToDefaultSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();\r\n            this.PreviewTrackbar = new System.Windows.Forms.TrackBar();\r\n            this.tabControl = new System.Windows.Forms.TabControl();\r\n            this.channelsTab = new System.Windows.Forms.TabPage();\r\n            this.ChannelsHelpLabel = new System.Windows.Forms.Label();\r\n            this.PropertyGrid = new System.Windows.Forms.PropertyGrid();\r\n            this.toolStrip1 = new System.Windows.Forms.ToolStrip();\r\n            this.AddFileButton = new System.Windows.Forms.ToolStripButton();\r\n            this.AddChannelButton = new System.Windows.Forms.ToolStripButton();\r\n            this.ChannelToolstripItemsSeparator = new System.Windows.Forms.ToolStripSeparator();\r\n            this.toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator();\r\n            this.RemoveLabelsButton = new System.Windows.Forms.ToolStripButton();\r\n            this.RemoveButton = new System.Windows.Forms.ToolStripDropDownButton();\r\n            this.removeselectedToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();\r\n            this.removeemptyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();\r\n            this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator();\r\n            this.removeallToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();\r\n            this.tabPage1 = new System.Windows.Forms.TabPage();\r\n            this.groupBox2 = new System.Windows.Forms.GroupBox();\r\n            this.MarginBottomControl = new System.Windows.Forms.NumericUpDown();\r\n            this.MarginRightControl = new System.Windows.Forms.NumericUpDown();\r\n            this.MarginLeftControl = new System.Windows.Forms.NumericUpDown();\r\n            this.MarginTopControl = new System.Windows.Forms.NumericUpDown();\r\n            this.label9 = new System.Windows.Forms.Label();\r\n            this.label8 = new System.Windows.Forms.Label();\r\n            this.label7 = new System.Windows.Forms.Label();\r\n            this.label6 = new System.Windows.Forms.Label();\r\n            this.AutoScaleButton = new System.Windows.Forms.Button();\r\n            this.label1 = new System.Windows.Forms.Label();\r\n            this.Columns = new System.Windows.Forms.NumericUpDown();\r\n            this.label16 = new System.Windows.Forms.Label();\r\n            this.label4 = new System.Windows.Forms.Label();\r\n            this.WidthControl = new System.Windows.Forms.TextBox();\r\n            this.label17 = new System.Windows.Forms.Label();\r\n            this.label5 = new System.Windows.Forms.Label();\r\n            this.VerticalScaling = new System.Windows.Forms.TextBox();\r\n            this.HeightControl = new System.Windows.Forms.TextBox();\r\n            this.label11 = new System.Windows.Forms.Label();\r\n            this.label12 = new System.Windows.Forms.Label();\r\n            this.tabPage4 = new System.Windows.Forms.TabPage();\r\n            this.label20 = new System.Windows.Forms.Label();\r\n            this.label21 = new System.Windows.Forms.Label();\r\n            this.BackgroundImageControl = new System.Windows.Forms.PictureBox();\r\n            this.tabPage3 = new System.Windows.Forms.TabPage();\r\n            this.label19 = new System.Windows.Forms.Label();\r\n            this.MasterAudioPath = new System.Windows.Forms.TextBox();\r\n            this.MasterMixReplayGain = new System.Windows.Forms.CheckBox();\r\n            this.AutogenerateMasterMix = new System.Windows.Forms.CheckBox();\r\n            this.tabPage2 = new System.Windows.Forms.TabPage();\r\n            this.RenderThreadsControl = new System.Windows.Forms.NumericUpDown();\r\n            this.label2 = new System.Windows.Forms.Label();\r\n            this.groupBox4 = new System.Windows.Forms.GroupBox();\r\n            this.PreviewCheckBox = new System.Windows.Forms.CheckBox();\r\n            this.label10 = new System.Windows.Forms.Label();\r\n            this.PreviewFrameskip = new System.Windows.Forms.NumericUpDown();\r\n            this.label13 = new System.Windows.Forms.Label();\r\n            this.groupBox3 = new System.Windows.Forms.GroupBox();\r\n            this.ExtraFFMPEGParameters = new System.Windows.Forms.TextBox();\r\n            this.label22 = new System.Windows.Forms.Label();\r\n            this.AudioCodec = new System.Windows.Forms.ComboBox();\r\n            this.label18 = new System.Windows.Forms.Label();\r\n            this.VideoCodec = new System.Windows.Forms.ComboBox();\r\n            this.label3 = new System.Windows.Forms.Label();\r\n            this.EncodeCheckBox = new System.Windows.Forms.CheckBox();\r\n            this.FrameRateControl = new System.Windows.Forms.NumericUpDown();\r\n            this.label14 = new System.Windows.Forms.Label();\r\n            this.label15 = new System.Windows.Forms.Label();\r\n            this.RenderButton = new System.Windows.Forms.Button();\r\n            this.tabPage6 = new System.Windows.Forms.TabPage();\r\n            this.LoadButton = new System.Windows.Forms.Button();\r\n            this.SaveButton = new System.Windows.Forms.Button();\r\n            this.tabPage5 = new System.Windows.Forms.TabPage();\r\n            this.ProgramSettingsGrid = new System.Windows.Forms.PropertyGrid();\r\n            this.imageList1 = new System.Windows.Forms.ImageList(this.components);\r\n            this.BackgroundColorButton = new SidWizPlusGUI.ColorButton(this.components);\r\n            ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();\r\n            this.splitContainer1.Panel1.SuspendLayout();\r\n            this.splitContainer1.Panel2.SuspendLayout();\r\n            this.splitContainer1.SuspendLayout();\r\n            ((System.ComponentModel.ISupportInitialize)(this.Preview)).BeginInit();\r\n            this.contextMenuStrip1.SuspendLayout();\r\n            ((System.ComponentModel.ISupportInitialize)(this.PreviewTrackbar)).BeginInit();\r\n            this.tabControl.SuspendLayout();\r\n            this.channelsTab.SuspendLayout();\r\n            this.toolStrip1.SuspendLayout();\r\n            this.tabPage1.SuspendLayout();\r\n            this.groupBox2.SuspendLayout();\r\n            ((System.ComponentModel.ISupportInitialize)(this.MarginBottomControl)).BeginInit();\r\n            ((System.ComponentModel.ISupportInitialize)(this.MarginRightControl)).BeginInit();\r\n            ((System.ComponentModel.ISupportInitialize)(this.MarginLeftControl)).BeginInit();\r\n            ((System.ComponentModel.ISupportInitialize)(this.MarginTopControl)).BeginInit();\r\n            ((System.ComponentModel.ISupportInitialize)(this.Columns)).BeginInit();\r\n            this.tabPage4.SuspendLayout();\r\n            ((System.ComponentModel.ISupportInitialize)(this.BackgroundImageControl)).BeginInit();\r\n            this.tabPage3.SuspendLayout();\r\n            this.tabPage2.SuspendLayout();\r\n            ((System.ComponentModel.ISupportInitialize)(this.RenderThreadsControl)).BeginInit();\r\n            this.groupBox4.SuspendLayout();\r\n            ((System.ComponentModel.ISupportInitialize)(this.PreviewFrameskip)).BeginInit();\r\n            this.groupBox3.SuspendLayout();\r\n            ((System.ComponentModel.ISupportInitialize)(this.FrameRateControl)).BeginInit();\r\n            this.tabPage6.SuspendLayout();\r\n            this.tabPage5.SuspendLayout();\r\n            this.SuspendLayout();\r\n            // \r\n            // splitContainer1\r\n            // \r\n            this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;\r\n            this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel2;\r\n            this.splitContainer1.Location = new System.Drawing.Point(0, 0);\r\n            this.splitContainer1.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.splitContainer1.Name = \"splitContainer1\";\r\n            // \r\n            // splitContainer1.Panel1\r\n            // \r\n            this.splitContainer1.Panel1.AutoScroll = true;\r\n            this.splitContainer1.Panel1.Controls.Add(this.Preview);\r\n            this.splitContainer1.Panel1.Controls.Add(this.PreviewTrackbar);\r\n            // \r\n            // splitContainer1.Panel2\r\n            // \r\n            this.splitContainer1.Panel2.Controls.Add(this.tabControl);\r\n            this.splitContainer1.Size = new System.Drawing.Size(1140, 648);\r\n            this.splitContainer1.SplitterDistance = 695;\r\n            this.splitContainer1.TabIndex = 0;\r\n            // \r\n            // Preview\r\n            // \r\n            this.Preview.ContextMenuStrip = this.contextMenuStrip1;\r\n            this.Preview.Dock = System.Windows.Forms.DockStyle.Fill;\r\n            this.Preview.Location = new System.Drawing.Point(0, 0);\r\n            this.Preview.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.Preview.Name = \"Preview\";\r\n            this.Preview.Size = new System.Drawing.Size(695, 603);\r\n            this.Preview.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;\r\n            this.Preview.TabIndex = 0;\r\n            this.Preview.TabStop = false;\r\n            this.Preview.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Preview_MouseClick);\r\n            // \r\n            // contextMenuStrip1\r\n            // \r\n            this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {\r\n            this.moveLeftupToolStripMenuItem,\r\n            this.moveRightdownToolStripMenuItem,\r\n            this.toolStripMenuItem1,\r\n            this.cloneChannelToolStripMenuItem,\r\n            this.removeChannelToolStripMenuItem,\r\n            this.splitChannelToolStripMenuItem,\r\n            this.toolStripSeparator3,\r\n            this.copySettingsToolStripMenuItem,\r\n            this.pasteSettingsToolStripMenuItem,\r\n            this.applySettingsToOtherChannelsToolStripMenuItem,\r\n            this.setAsdefaultsToolStripMenuItem,\r\n            this.resetToDefaultSettingsToolStripMenuItem});\r\n            this.contextMenuStrip1.Name = \"contextMenuStrip1\";\r\n            this.contextMenuStrip1.Size = new System.Drawing.Size(245, 236);\r\n            // \r\n            // moveLeftupToolStripMenuItem\r\n            // \r\n            this.moveLeftupToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject(\"moveLeftupToolStripMenuItem.Image\")));\r\n            this.moveLeftupToolStripMenuItem.Name = \"moveLeftupToolStripMenuItem\";\r\n            this.moveLeftupToolStripMenuItem.Size = new System.Drawing.Size(244, 22);\r\n            this.moveLeftupToolStripMenuItem.Text = \"Move &left/up\";\r\n            this.moveLeftupToolStripMenuItem.Click += new System.EventHandler(this.LeftButton_Click);\r\n            // \r\n            // moveRightdownToolStripMenuItem\r\n            // \r\n            this.moveRightdownToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject(\"moveRightdownToolStripMenuItem.Image\")));\r\n            this.moveRightdownToolStripMenuItem.Name = \"moveRightdownToolStripMenuItem\";\r\n            this.moveRightdownToolStripMenuItem.Size = new System.Drawing.Size(244, 22);\r\n            this.moveRightdownToolStripMenuItem.Text = \"Move &right/down\";\r\n            this.moveRightdownToolStripMenuItem.Click += new System.EventHandler(this.RightButton_Click);\r\n            // \r\n            // toolStripMenuItem1\r\n            // \r\n            this.toolStripMenuItem1.Name = \"toolStripMenuItem1\";\r\n            this.toolStripMenuItem1.Size = new System.Drawing.Size(241, 6);\r\n            // \r\n            // cloneChannelToolStripMenuItem\r\n            // \r\n            this.cloneChannelToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject(\"cloneChannelToolStripMenuItem.Image\")));\r\n            this.cloneChannelToolStripMenuItem.Name = \"cloneChannelToolStripMenuItem\";\r\n            this.cloneChannelToolStripMenuItem.Size = new System.Drawing.Size(244, 22);\r\n            this.cloneChannelToolStripMenuItem.Text = \"Cl&one\";\r\n            this.cloneChannelToolStripMenuItem.Click += new System.EventHandler(this.CloneChannelButton_Click);\r\n            // \r\n            // removeChannelToolStripMenuItem\r\n            // \r\n            this.removeChannelToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject(\"removeChannelToolStripMenuItem.Image\")));\r\n            this.removeChannelToolStripMenuItem.Name = \"removeChannelToolStripMenuItem\";\r\n            this.removeChannelToolStripMenuItem.Size = new System.Drawing.Size(244, 22);\r\n            this.removeChannelToolStripMenuItem.Text = \"Re&move\";\r\n            this.removeChannelToolStripMenuItem.Click += new System.EventHandler(this.RemoveButton_Click);\r\n            // \r\n            // splitChannelToolStripMenuItem\r\n            // \r\n            this.splitChannelToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject(\"splitChannelToolStripMenuItem.Image\")));\r\n            this.splitChannelToolStripMenuItem.Name = \"splitChannelToolStripMenuItem\";\r\n            this.splitChannelToolStripMenuItem.Size = new System.Drawing.Size(244, 22);\r\n            this.splitChannelToolStripMenuItem.Text = \"&Split\";\r\n            this.splitChannelToolStripMenuItem.Click += new System.EventHandler(this.SplitChannel);\r\n            // \r\n            // toolStripSeparator3\r\n            // \r\n            this.toolStripSeparator3.Name = \"toolStripSeparator3\";\r\n            this.toolStripSeparator3.Size = new System.Drawing.Size(241, 6);\r\n            // \r\n            // copySettingsToolStripMenuItem\r\n            // \r\n            this.copySettingsToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject(\"copySettingsToolStripMenuItem.Image\")));\r\n            this.copySettingsToolStripMenuItem.Name = \"copySettingsToolStripMenuItem\";\r\n            this.copySettingsToolStripMenuItem.Size = new System.Drawing.Size(244, 22);\r\n            this.copySettingsToolStripMenuItem.Text = \"&Copy settings\";\r\n            this.copySettingsToolStripMenuItem.Click += new System.EventHandler(this.CopyChannelSettings);\r\n            // \r\n            // pasteSettingsToolStripMenuItem\r\n            // \r\n            this.pasteSettingsToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject(\"pasteSettingsToolStripMenuItem.Image\")));\r\n            this.pasteSettingsToolStripMenuItem.Name = \"pasteSettingsToolStripMenuItem\";\r\n            this.pasteSettingsToolStripMenuItem.Size = new System.Drawing.Size(244, 22);\r\n            this.pasteSettingsToolStripMenuItem.Text = \"&Paste settings\";\r\n            this.pasteSettingsToolStripMenuItem.Click += new System.EventHandler(this.PasteChannelSettings);\r\n            // \r\n            // applySettingsToOtherChannelsToolStripMenuItem\r\n            // \r\n            this.applySettingsToOtherChannelsToolStripMenuItem.Image = global::SidWizPlusGUI.Properties.Resources.lightning;\r\n            this.applySettingsToOtherChannelsToolStripMenuItem.Name = \"applySettingsToOtherChannelsToolStripMenuItem\";\r\n            this.applySettingsToOtherChannelsToolStripMenuItem.Size = new System.Drawing.Size(244, 22);\r\n            this.applySettingsToOtherChannelsToolStripMenuItem.Text = \"&Apply settings to other channels\";\r\n            this.applySettingsToOtherChannelsToolStripMenuItem.Click += new System.EventHandler(this.CopySettingsButton_Click);\r\n            // \r\n            // setAsdefaultsToolStripMenuItem\r\n            // \r\n            this.setAsdefaultsToolStripMenuItem.Image = global::SidWizPlusGUI.Properties.Resources.page_save;\r\n            this.setAsdefaultsToolStripMenuItem.Name = \"setAsdefaultsToolStripMenuItem\";\r\n            this.setAsdefaultsToolStripMenuItem.Size = new System.Drawing.Size(244, 22);\r\n            this.setAsdefaultsToolStripMenuItem.Text = \"Save as &default settings\";\r\n            this.setAsdefaultsToolStripMenuItem.Click += new System.EventHandler(this.SaveAsDefaultSettings);\r\n            // \r\n            // resetToDefaultSettingsToolStripMenuItem\r\n            // \r\n            this.resetToDefaultSettingsToolStripMenuItem.Image = global::SidWizPlusGUI.Properties.Resources.page_refresh;\r\n            this.resetToDefaultSettingsToolStripMenuItem.Name = \"resetToDefaultSettingsToolStripMenuItem\";\r\n            this.resetToDefaultSettingsToolStripMenuItem.Size = new System.Drawing.Size(244, 22);\r\n            this.resetToDefaultSettingsToolStripMenuItem.Text = \"R&eset to default settings\";\r\n            this.resetToDefaultSettingsToolStripMenuItem.Click += new System.EventHandler(this.ResetToDefaultSettings);\r\n            // \r\n            // PreviewTrackbar\r\n            // \r\n            this.PreviewTrackbar.Dock = System.Windows.Forms.DockStyle.Bottom;\r\n            this.PreviewTrackbar.LargeChange = 60;\r\n            this.PreviewTrackbar.Location = new System.Drawing.Point(0, 603);\r\n            this.PreviewTrackbar.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);\r\n            this.PreviewTrackbar.Maximum = 1000;\r\n            this.PreviewTrackbar.Name = \"PreviewTrackbar\";\r\n            this.PreviewTrackbar.Size = new System.Drawing.Size(695, 45);\r\n            this.PreviewTrackbar.TabIndex = 0;\r\n            this.PreviewTrackbar.TickFrequency = 60;\r\n            this.PreviewTrackbar.ValueChanged += new System.EventHandler(this.ControlValueChanged);\r\n            // \r\n            // tabControl\r\n            // \r\n            this.tabControl.Controls.Add(this.channelsTab);\r\n            this.tabControl.Controls.Add(this.tabPage1);\r\n            this.tabControl.Controls.Add(this.tabPage4);\r\n            this.tabControl.Controls.Add(this.tabPage3);\r\n            this.tabControl.Controls.Add(this.tabPage2);\r\n            this.tabControl.Controls.Add(this.tabPage6);\r\n            this.tabControl.Controls.Add(this.tabPage5);\r\n            this.tabControl.Dock = System.Windows.Forms.DockStyle.Fill;\r\n            this.tabControl.ImageList = this.imageList1;\r\n            this.tabControl.Location = new System.Drawing.Point(0, 0);\r\n            this.tabControl.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.tabControl.Multiline = true;\r\n            this.tabControl.Name = \"tabControl\";\r\n            this.tabControl.SelectedIndex = 0;\r\n            this.tabControl.Size = new System.Drawing.Size(441, 648);\r\n            this.tabControl.SizeMode = System.Windows.Forms.TabSizeMode.FillToRight;\r\n            this.tabControl.TabIndex = 0;\r\n            // \r\n            // channelsTab\r\n            // \r\n            this.channelsTab.Controls.Add(this.ChannelsHelpLabel);\r\n            this.channelsTab.Controls.Add(this.PropertyGrid);\r\n            this.channelsTab.Controls.Add(this.toolStrip1);\r\n            this.channelsTab.ImageKey = \"music.png\";\r\n            this.channelsTab.Location = new System.Drawing.Point(4, 44);\r\n            this.channelsTab.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);\r\n            this.channelsTab.Name = \"channelsTab\";\r\n            this.channelsTab.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4);\r\n            this.channelsTab.Size = new System.Drawing.Size(433, 600);\r\n            this.channelsTab.TabIndex = 4;\r\n            this.channelsTab.Text = \"Channels\";\r\n            this.channelsTab.UseVisualStyleBackColor = true;\r\n            // \r\n            // ChannelsHelpLabel\r\n            // \r\n            this.ChannelsHelpLabel.Dock = System.Windows.Forms.DockStyle.Fill;\r\n            this.ChannelsHelpLabel.Location = new System.Drawing.Point(3, 29);\r\n            this.ChannelsHelpLabel.Name = \"ChannelsHelpLabel\";\r\n            this.ChannelsHelpLabel.Size = new System.Drawing.Size(427, 567);\r\n            this.ChannelsHelpLabel.TabIndex = 2;\r\n            this.ChannelsHelpLabel.Text = \"Click a channel to view its properties\\r\\n\\r\\nOpen files using the first toolbar butt\" +\r\n    \"on to add channels\";\r\n            this.ChannelsHelpLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;\r\n            // \r\n            // PropertyGrid\r\n            // \r\n            this.PropertyGrid.Dock = System.Windows.Forms.DockStyle.Fill;\r\n            this.PropertyGrid.Location = new System.Drawing.Point(3, 29);\r\n            this.PropertyGrid.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.PropertyGrid.Name = \"PropertyGrid\";\r\n            this.PropertyGrid.Size = new System.Drawing.Size(427, 567);\r\n            this.PropertyGrid.TabIndex = 1;\r\n            this.PropertyGrid.ToolbarVisible = false;\r\n            this.PropertyGrid.SelectedObjectsChanged += new System.EventHandler(this.PropertyGrid_SelectedObjectsChanged);\r\n            // \r\n            // toolStrip1\r\n            // \r\n            this.toolStrip1.CanOverflow = false;\r\n            this.toolStrip1.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden;\r\n            this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {\r\n            this.AddFileButton,\r\n            this.AddChannelButton,\r\n            this.ChannelToolstripItemsSeparator,\r\n            this.toolStripSeparator5,\r\n            this.RemoveLabelsButton,\r\n            this.RemoveButton});\r\n            this.toolStrip1.Location = new System.Drawing.Point(3, 4);\r\n            this.toolStrip1.Name = \"toolStrip1\";\r\n            this.toolStrip1.Padding = new System.Windows.Forms.Padding(0);\r\n            this.toolStrip1.Size = new System.Drawing.Size(427, 25);\r\n            this.toolStrip1.TabIndex = 0;\r\n            this.toolStrip1.Text = \"toolStrip1\";\r\n            // \r\n            // AddFileButton\r\n            // \r\n            this.AddFileButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;\r\n            this.AddFileButton.Image = ((System.Drawing.Image)(resources.GetObject(\"AddFileButton.Image\")));\r\n            this.AddFileButton.ImageTransparentColor = System.Drawing.Color.Magenta;\r\n            this.AddFileButton.Name = \"AddFileButton\";\r\n            this.AddFileButton.Size = new System.Drawing.Size(23, 22);\r\n            this.AddFileButton.Text = \"Add a file\";\r\n            this.AddFileButton.Click += new System.EventHandler(this.AddAFileClick);\r\n            // \r\n            // AddChannelButton\r\n            // \r\n            this.AddChannelButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;\r\n            this.AddChannelButton.Image = global::SidWizPlusGUI.Properties.Resources.page;\r\n            this.AddChannelButton.ImageTransparentColor = System.Drawing.Color.Magenta;\r\n            this.AddChannelButton.Name = \"AddChannelButton\";\r\n            this.AddChannelButton.Size = new System.Drawing.Size(23, 22);\r\n            this.AddChannelButton.Text = \"Add a channel\";\r\n            this.AddChannelButton.Click += new System.EventHandler(this.AddChannelButton_Click);\r\n            // \r\n            // ChannelToolstripItemsSeparator\r\n            // \r\n            this.ChannelToolstripItemsSeparator.Name = \"ChannelToolstripItemsSeparator\";\r\n            this.ChannelToolstripItemsSeparator.Size = new System.Drawing.Size(6, 25);\r\n            // \r\n            // toolStripSeparator5\r\n            // \r\n            this.toolStripSeparator5.Name = \"toolStripSeparator5\";\r\n            this.toolStripSeparator5.Size = new System.Drawing.Size(6, 25);\r\n            // \r\n            // RemoveLabelsButton\r\n            // \r\n            this.RemoveLabelsButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;\r\n            this.RemoveLabelsButton.Image = ((System.Drawing.Image)(resources.GetObject(\"RemoveLabelsButton.Image\")));\r\n            this.RemoveLabelsButton.ImageTransparentColor = System.Drawing.Color.Magenta;\r\n            this.RemoveLabelsButton.Name = \"RemoveLabelsButton\";\r\n            this.RemoveLabelsButton.Size = new System.Drawing.Size(23, 22);\r\n            this.RemoveLabelsButton.Text = \"Remove labels\";\r\n            this.RemoveLabelsButton.Click += new System.EventHandler(this.RemoveAllLabels);\r\n            // \r\n            // RemoveButton\r\n            // \r\n            this.RemoveButton.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right;\r\n            this.RemoveButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;\r\n            this.RemoveButton.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {\r\n            this.removeselectedToolStripMenuItem,\r\n            this.removeemptyToolStripMenuItem,\r\n            this.toolStripMenuItem2,\r\n            this.removeallToolStripMenuItem});\r\n            this.RemoveButton.Image = ((System.Drawing.Image)(resources.GetObject(\"RemoveButton.Image\")));\r\n            this.RemoveButton.ImageTransparentColor = System.Drawing.Color.Magenta;\r\n            this.RemoveButton.Name = \"RemoveButton\";\r\n            this.RemoveButton.Size = new System.Drawing.Size(29, 22);\r\n            this.RemoveButton.Text = \"Remove channel\";\r\n            // \r\n            // removeselectedToolStripMenuItem\r\n            // \r\n            this.removeselectedToolStripMenuItem.Name = \"removeselectedToolStripMenuItem\";\r\n            this.removeselectedToolStripMenuItem.Size = new System.Drawing.Size(163, 22);\r\n            this.removeselectedToolStripMenuItem.Text = \"Remove &selected\";\r\n            this.removeselectedToolStripMenuItem.Click += new System.EventHandler(this.RemoveButton_Click);\r\n            // \r\n            // removeemptyToolStripMenuItem\r\n            // \r\n            this.removeemptyToolStripMenuItem.Name = \"removeemptyToolStripMenuItem\";\r\n            this.removeemptyToolStripMenuItem.Size = new System.Drawing.Size(163, 22);\r\n            this.removeemptyToolStripMenuItem.Text = \"Remove s&ilent\";\r\n            this.removeemptyToolStripMenuItem.Click += new System.EventHandler(this.RemoveSilentChannels);\r\n            // \r\n            // toolStripMenuItem2\r\n            // \r\n            this.toolStripMenuItem2.Name = \"toolStripMenuItem2\";\r\n            this.toolStripMenuItem2.Size = new System.Drawing.Size(160, 6);\r\n            // \r\n            // removeallToolStripMenuItem\r\n            // \r\n            this.removeallToolStripMenuItem.Name = \"removeallToolStripMenuItem\";\r\n            this.removeallToolStripMenuItem.Size = new System.Drawing.Size(163, 22);\r\n            this.removeallToolStripMenuItem.Text = \"Remove &all\";\r\n            this.removeallToolStripMenuItem.Click += new System.EventHandler(this.RemoveAllChannels);\r\n            // \r\n            // tabPage1\r\n            // \r\n            this.tabPage1.Controls.Add(this.groupBox2);\r\n            this.tabPage1.Controls.Add(this.AutoScaleButton);\r\n            this.tabPage1.Controls.Add(this.label1);\r\n            this.tabPage1.Controls.Add(this.Columns);\r\n            this.tabPage1.Controls.Add(this.label16);\r\n            this.tabPage1.Controls.Add(this.label4);\r\n            this.tabPage1.Controls.Add(this.WidthControl);\r\n            this.tabPage1.Controls.Add(this.label17);\r\n            this.tabPage1.Controls.Add(this.label5);\r\n            this.tabPage1.Controls.Add(this.VerticalScaling);\r\n            this.tabPage1.Controls.Add(this.HeightControl);\r\n            this.tabPage1.Controls.Add(this.label11);\r\n            this.tabPage1.Controls.Add(this.label12);\r\n            this.tabPage1.ImageKey = \"layout.png\";\r\n            this.tabPage1.Location = new System.Drawing.Point(4, 44);\r\n            this.tabPage1.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.tabPage1.Name = \"tabPage1\";\r\n            this.tabPage1.Padding = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.tabPage1.Size = new System.Drawing.Size(433, 600);\r\n            this.tabPage1.TabIndex = 0;\r\n            this.tabPage1.Text = \"Layout\";\r\n            this.tabPage1.UseVisualStyleBackColor = true;\r\n            // \r\n            // groupBox2\r\n            // \r\n            this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \r\n            | System.Windows.Forms.AnchorStyles.Right)));\r\n            this.groupBox2.Controls.Add(this.MarginBottomControl);\r\n            this.groupBox2.Controls.Add(this.MarginRightControl);\r\n            this.groupBox2.Controls.Add(this.MarginLeftControl);\r\n            this.groupBox2.Controls.Add(this.MarginTopControl);\r\n            this.groupBox2.Controls.Add(this.label9);\r\n            this.groupBox2.Controls.Add(this.label8);\r\n            this.groupBox2.Controls.Add(this.label7);\r\n            this.groupBox2.Controls.Add(this.label6);\r\n            this.groupBox2.Location = new System.Drawing.Point(4, 93);\r\n            this.groupBox2.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.groupBox2.Name = \"groupBox2\";\r\n            this.groupBox2.Padding = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.groupBox2.Size = new System.Drawing.Size(425, 146);\r\n            this.groupBox2.TabIndex = 8;\r\n            this.groupBox2.TabStop = false;\r\n            this.groupBox2.Text = \"Margins\";\r\n            // \r\n            // MarginBottomControl\r\n            // \r\n            this.MarginBottomControl.Location = new System.Drawing.Point(114, 109);\r\n            this.MarginBottomControl.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.MarginBottomControl.Maximum = new decimal(new int[] {\r\n            1000,\r\n            0,\r\n            0,\r\n            0});\r\n            this.MarginBottomControl.Name = \"MarginBottomControl\";\r\n            this.MarginBottomControl.Size = new System.Drawing.Size(61, 23);\r\n            this.MarginBottomControl.TabIndex = 7;\r\n            this.MarginBottomControl.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;\r\n            this.MarginBottomControl.ValueChanged += new System.EventHandler(this.ControlValueChanged);\r\n            // \r\n            // MarginRightControl\r\n            // \r\n            this.MarginRightControl.Location = new System.Drawing.Point(114, 80);\r\n            this.MarginRightControl.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.MarginRightControl.Maximum = new decimal(new int[] {\r\n            1000,\r\n            0,\r\n            0,\r\n            0});\r\n            this.MarginRightControl.Name = \"MarginRightControl\";\r\n            this.MarginRightControl.Size = new System.Drawing.Size(61, 23);\r\n            this.MarginRightControl.TabIndex = 5;\r\n            this.MarginRightControl.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;\r\n            this.MarginRightControl.ValueChanged += new System.EventHandler(this.ControlValueChanged);\r\n            // \r\n            // MarginLeftControl\r\n            // \r\n            this.MarginLeftControl.Location = new System.Drawing.Point(114, 51);\r\n            this.MarginLeftControl.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.MarginLeftControl.Maximum = new decimal(new int[] {\r\n            1000,\r\n            0,\r\n            0,\r\n            0});\r\n            this.MarginLeftControl.Name = \"MarginLeftControl\";\r\n            this.MarginLeftControl.Size = new System.Drawing.Size(61, 23);\r\n            this.MarginLeftControl.TabIndex = 3;\r\n            this.MarginLeftControl.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;\r\n            this.MarginLeftControl.ValueChanged += new System.EventHandler(this.ControlValueChanged);\r\n            // \r\n            // MarginTopControl\r\n            // \r\n            this.MarginTopControl.Location = new System.Drawing.Point(114, 22);\r\n            this.MarginTopControl.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.MarginTopControl.Maximum = new decimal(new int[] {\r\n            1000,\r\n            0,\r\n            0,\r\n            0});\r\n            this.MarginTopControl.Name = \"MarginTopControl\";\r\n            this.MarginTopControl.Size = new System.Drawing.Size(61, 23);\r\n            this.MarginTopControl.TabIndex = 1;\r\n            this.MarginTopControl.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;\r\n            this.MarginTopControl.ValueChanged += new System.EventHandler(this.ControlValueChanged);\r\n            // \r\n            // label9\r\n            // \r\n            this.label9.AutoSize = true;\r\n            this.label9.Location = new System.Drawing.Point(10, 111);\r\n            this.label9.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);\r\n            this.label9.Name = \"label9\";\r\n            this.label9.Size = new System.Drawing.Size(47, 15);\r\n            this.label9.TabIndex = 6;\r\n            this.label9.Text = \"Bottom\";\r\n            // \r\n            // label8\r\n            // \r\n            this.label8.AutoSize = true;\r\n            this.label8.Location = new System.Drawing.Point(10, 82);\r\n            this.label8.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);\r\n            this.label8.Name = \"label8\";\r\n            this.label8.Size = new System.Drawing.Size(35, 15);\r\n            this.label8.TabIndex = 4;\r\n            this.label8.Text = \"Right\";\r\n            // \r\n            // label7\r\n            // \r\n            this.label7.AutoSize = true;\r\n            this.label7.Location = new System.Drawing.Point(10, 53);\r\n            this.label7.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);\r\n            this.label7.Name = \"label7\";\r\n            this.label7.Size = new System.Drawing.Size(27, 15);\r\n            this.label7.TabIndex = 2;\r\n            this.label7.Text = \"Left\";\r\n            // \r\n            // label6\r\n            // \r\n            this.label6.AutoSize = true;\r\n            this.label6.Location = new System.Drawing.Point(10, 24);\r\n            this.label6.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);\r\n            this.label6.Name = \"label6\";\r\n            this.label6.Size = new System.Drawing.Size(26, 15);\r\n            this.label6.TabIndex = 0;\r\n            this.label6.Text = \"Top\";\r\n            // \r\n            // AutoScaleButton\r\n            // \r\n            this.AutoScaleButton.Location = new System.Drawing.Point(118, 274);\r\n            this.AutoScaleButton.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.AutoScaleButton.Name = \"AutoScaleButton\";\r\n            this.AutoScaleButton.Size = new System.Drawing.Size(61, 33);\r\n            this.AutoScaleButton.TabIndex = 12;\r\n            this.AutoScaleButton.Text = \"Apply\";\r\n            this.AutoScaleButton.UseVisualStyleBackColor = true;\r\n            this.AutoScaleButton.Click += new System.EventHandler(this.AutoScale_Click);\r\n            // \r\n            // label1\r\n            // \r\n            this.label1.AutoSize = true;\r\n            this.label1.Location = new System.Drawing.Point(14, 66);\r\n            this.label1.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);\r\n            this.label1.Name = \"label1\";\r\n            this.label1.Size = new System.Drawing.Size(55, 15);\r\n            this.label1.TabIndex = 6;\r\n            this.label1.Text = \"Columns\";\r\n            // \r\n            // Columns\r\n            // \r\n            this.Columns.Location = new System.Drawing.Point(118, 64);\r\n            this.Columns.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.Columns.Minimum = new decimal(new int[] {\r\n            1,\r\n            0,\r\n            0,\r\n            0});\r\n            this.Columns.Name = \"Columns\";\r\n            this.Columns.Size = new System.Drawing.Size(61, 23);\r\n            this.Columns.TabIndex = 7;\r\n            this.Columns.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;\r\n            this.Columns.Value = new decimal(new int[] {\r\n            1,\r\n            0,\r\n            0,\r\n            0});\r\n            this.Columns.ValueChanged += new System.EventHandler(this.ControlValueChanged);\r\n            // \r\n            // label16\r\n            // \r\n            this.label16.AutoSize = true;\r\n            this.label16.Location = new System.Drawing.Point(182, 248);\r\n            this.label16.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);\r\n            this.label16.Name = \"label16\";\r\n            this.label16.Size = new System.Drawing.Size(59, 15);\r\n            this.label16.TabIndex = 11;\r\n            this.label16.Text = \"% of peak\";\r\n            // \r\n            // label4\r\n            // \r\n            this.label4.AutoSize = true;\r\n            this.label4.Location = new System.Drawing.Point(14, 9);\r\n            this.label4.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);\r\n            this.label4.Name = \"label4\";\r\n            this.label4.Size = new System.Drawing.Size(39, 15);\r\n            this.label4.TabIndex = 0;\r\n            this.label4.Text = \"Width\";\r\n            // \r\n            // WidthControl\r\n            // \r\n            this.WidthControl.Location = new System.Drawing.Point(118, 6);\r\n            this.WidthControl.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.WidthControl.Name = \"WidthControl\";\r\n            this.WidthControl.Size = new System.Drawing.Size(60, 23);\r\n            this.WidthControl.TabIndex = 1;\r\n            this.WidthControl.Text = \"1280\";\r\n            this.WidthControl.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;\r\n            this.WidthControl.TextChanged += new System.EventHandler(this.ControlValueChanged);\r\n            // \r\n            // label17\r\n            // \r\n            this.label17.AutoSize = true;\r\n            this.label17.Location = new System.Drawing.Point(14, 248);\r\n            this.label17.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);\r\n            this.label17.Name = \"label17\";\r\n            this.label17.Size = new System.Drawing.Size(85, 15);\r\n            this.label17.TabIndex = 9;\r\n            this.label17.Text = \"Vertical scaling\";\r\n            // \r\n            // label5\r\n            // \r\n            this.label5.AutoSize = true;\r\n            this.label5.Location = new System.Drawing.Point(14, 38);\r\n            this.label5.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);\r\n            this.label5.Name = \"label5\";\r\n            this.label5.Size = new System.Drawing.Size(43, 15);\r\n            this.label5.TabIndex = 3;\r\n            this.label5.Text = \"Height\";\r\n            // \r\n            // VerticalScaling\r\n            // \r\n            this.VerticalScaling.Location = new System.Drawing.Point(118, 245);\r\n            this.VerticalScaling.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.VerticalScaling.Name = \"VerticalScaling\";\r\n            this.VerticalScaling.Size = new System.Drawing.Size(60, 23);\r\n            this.VerticalScaling.TabIndex = 10;\r\n            this.VerticalScaling.Text = \"80\";\r\n            this.VerticalScaling.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;\r\n            this.VerticalScaling.TextChanged += new System.EventHandler(this.ControlValueChanged);\r\n            // \r\n            // HeightControl\r\n            // \r\n            this.HeightControl.Location = new System.Drawing.Point(118, 35);\r\n            this.HeightControl.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.HeightControl.Name = \"HeightControl\";\r\n            this.HeightControl.Size = new System.Drawing.Size(60, 23);\r\n            this.HeightControl.TabIndex = 4;\r\n            this.HeightControl.Text = \"720\";\r\n            this.HeightControl.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;\r\n            this.HeightControl.TextChanged += new System.EventHandler(this.ControlValueChanged);\r\n            // \r\n            // label11\r\n            // \r\n            this.label11.AutoSize = true;\r\n            this.label11.Location = new System.Drawing.Point(182, 9);\r\n            this.label11.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);\r\n            this.label11.Name = \"label11\";\r\n            this.label11.Size = new System.Drawing.Size(20, 15);\r\n            this.label11.TabIndex = 2;\r\n            this.label11.Text = \"px\";\r\n            // \r\n            // label12\r\n            // \r\n            this.label12.AutoSize = true;\r\n            this.label12.Location = new System.Drawing.Point(182, 38);\r\n            this.label12.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);\r\n            this.label12.Name = \"label12\";\r\n            this.label12.Size = new System.Drawing.Size(20, 15);\r\n            this.label12.TabIndex = 5;\r\n            this.label12.Text = \"px\";\r\n            // \r\n            // tabPage4\r\n            // \r\n            this.tabPage4.Controls.Add(this.label20);\r\n            this.tabPage4.Controls.Add(this.label21);\r\n            this.tabPage4.Controls.Add(this.BackgroundImageControl);\r\n            this.tabPage4.Controls.Add(this.BackgroundColorButton);\r\n            this.tabPage4.ImageKey = \"picture.png\";\r\n            this.tabPage4.Location = new System.Drawing.Point(4, 44);\r\n            this.tabPage4.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.tabPage4.Name = \"tabPage4\";\r\n            this.tabPage4.Padding = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.tabPage4.Size = new System.Drawing.Size(433, 600);\r\n            this.tabPage4.TabIndex = 3;\r\n            this.tabPage4.Text = \"Background\";\r\n            this.tabPage4.UseVisualStyleBackColor = true;\r\n            // \r\n            // label20\r\n            // \r\n            this.label20.AutoSize = true;\r\n            this.label20.Location = new System.Drawing.Point(14, 15);\r\n            this.label20.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);\r\n            this.label20.Name = \"label20\";\r\n            this.label20.Size = new System.Drawing.Size(36, 15);\r\n            this.label20.TabIndex = 0;\r\n            this.label20.Text = \"Color\";\r\n            // \r\n            // label21\r\n            // \r\n            this.label21.AutoSize = true;\r\n            this.label21.Location = new System.Drawing.Point(14, 49);\r\n            this.label21.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);\r\n            this.label21.Name = \"label21\";\r\n            this.label21.Size = new System.Drawing.Size(40, 15);\r\n            this.label21.TabIndex = 2;\r\n            this.label21.Text = \"Image\";\r\n            // \r\n            // BackgroundImageControl\r\n            // \r\n            this.BackgroundImageControl.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;\r\n            this.BackgroundImageControl.Cursor = System.Windows.Forms.Cursors.Hand;\r\n            this.BackgroundImageControl.Location = new System.Drawing.Point(119, 45);\r\n            this.BackgroundImageControl.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.BackgroundImageControl.Name = \"BackgroundImageControl\";\r\n            this.BackgroundImageControl.Size = new System.Drawing.Size(113, 97);\r\n            this.BackgroundImageControl.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;\r\n            this.BackgroundImageControl.TabIndex = 16;\r\n            this.BackgroundImageControl.TabStop = false;\r\n            this.BackgroundImageControl.Click += new System.EventHandler(this.BackgroundImageControl_Click);\r\n            // \r\n            // tabPage3\r\n            // \r\n            this.tabPage3.Controls.Add(this.label19);\r\n            this.tabPage3.Controls.Add(this.MasterAudioPath);\r\n            this.tabPage3.Controls.Add(this.MasterMixReplayGain);\r\n            this.tabPage3.Controls.Add(this.AutogenerateMasterMix);\r\n            this.tabPage3.ImageKey = \"sound.png\";\r\n            this.tabPage3.Location = new System.Drawing.Point(4, 44);\r\n            this.tabPage3.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.tabPage3.Name = \"tabPage3\";\r\n            this.tabPage3.Padding = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.tabPage3.Size = new System.Drawing.Size(433, 600);\r\n            this.tabPage3.TabIndex = 2;\r\n            this.tabPage3.Text = \"Audio\";\r\n            this.tabPage3.UseVisualStyleBackColor = true;\r\n            // \r\n            // label19\r\n            // \r\n            this.label19.AutoSize = true;\r\n            this.label19.Location = new System.Drawing.Point(11, 59);\r\n            this.label19.Name = \"label19\";\r\n            this.label19.Size = new System.Drawing.Size(53, 15);\r\n            this.label19.TabIndex = 3;\r\n            this.label19.Text = \"Location\";\r\n            // \r\n            // MasterAudioPath\r\n            // \r\n            this.MasterAudioPath.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \r\n            | System.Windows.Forms.AnchorStyles.Right)));\r\n            this.MasterAudioPath.Cursor = System.Windows.Forms.Cursors.Hand;\r\n            this.MasterAudioPath.Location = new System.Drawing.Point(14, 78);\r\n            this.MasterAudioPath.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);\r\n            this.MasterAudioPath.Name = \"MasterAudioPath\";\r\n            this.MasterAudioPath.ReadOnly = true;\r\n            this.MasterAudioPath.Size = new System.Drawing.Size(411, 23);\r\n            this.MasterAudioPath.TabIndex = 4;\r\n            this.MasterAudioPath.Click += new System.EventHandler(this.SetMasterAudioPath);\r\n            // \r\n            // MasterMixReplayGain\r\n            // \r\n            this.MasterMixReplayGain.AutoSize = true;\r\n            this.MasterMixReplayGain.Checked = true;\r\n            this.MasterMixReplayGain.CheckState = System.Windows.Forms.CheckState.Checked;\r\n            this.MasterMixReplayGain.Location = new System.Drawing.Point(14, 34);\r\n            this.MasterMixReplayGain.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.MasterMixReplayGain.Name = \"MasterMixReplayGain\";\r\n            this.MasterMixReplayGain.Size = new System.Drawing.Size(119, 19);\r\n            this.MasterMixReplayGain.TabIndex = 1;\r\n            this.MasterMixReplayGain.Text = \"Apply ReplayGain\";\r\n            this.MasterMixReplayGain.UseVisualStyleBackColor = true;\r\n            // \r\n            // AutogenerateMasterMix\r\n            // \r\n            this.AutogenerateMasterMix.AutoSize = true;\r\n            this.AutogenerateMasterMix.Checked = true;\r\n            this.AutogenerateMasterMix.CheckState = System.Windows.Forms.CheckState.Checked;\r\n            this.AutogenerateMasterMix.Location = new System.Drawing.Point(14, 9);\r\n            this.AutogenerateMasterMix.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.AutogenerateMasterMix.Name = \"AutogenerateMasterMix\";\r\n            this.AutogenerateMasterMix.Size = new System.Drawing.Size(189, 19);\r\n            this.AutogenerateMasterMix.TabIndex = 0;\r\n            this.AutogenerateMasterMix.Text = \"Autogenerate master audio file\";\r\n            this.AutogenerateMasterMix.UseVisualStyleBackColor = true;\r\n            this.AutogenerateMasterMix.CheckedChanged += new System.EventHandler(this.AutogenerateMasterMix_CheckedChanged);\r\n            // \r\n            // tabPage2\r\n            // \r\n            this.tabPage2.Controls.Add(this.RenderThreadsControl);\r\n            this.tabPage2.Controls.Add(this.label2);\r\n            this.tabPage2.Controls.Add(this.groupBox4);\r\n            this.tabPage2.Controls.Add(this.groupBox3);\r\n            this.tabPage2.Controls.Add(this.FrameRateControl);\r\n            this.tabPage2.Controls.Add(this.label14);\r\n            this.tabPage2.Controls.Add(this.label15);\r\n            this.tabPage2.Controls.Add(this.RenderButton);\r\n            this.tabPage2.ImageKey = \"film.png\";\r\n            this.tabPage2.Location = new System.Drawing.Point(4, 44);\r\n            this.tabPage2.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);\r\n            this.tabPage2.Name = \"tabPage2\";\r\n            this.tabPage2.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4);\r\n            this.tabPage2.Size = new System.Drawing.Size(433, 600);\r\n            this.tabPage2.TabIndex = 5;\r\n            this.tabPage2.Text = \"Video\";\r\n            this.tabPage2.UseVisualStyleBackColor = true;\r\n            // \r\n            // RenderThreadsControl\r\n            // \r\n            this.RenderThreadsControl.Location = new System.Drawing.Point(118, 265);\r\n            this.RenderThreadsControl.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.RenderThreadsControl.Maximum = new decimal(new int[] {\r\n            1000,\r\n            0,\r\n            0,\r\n            0});\r\n            this.RenderThreadsControl.Minimum = new decimal(new int[] {\r\n            1,\r\n            0,\r\n            0,\r\n            0});\r\n            this.RenderThreadsControl.Name = \"RenderThreadsControl\";\r\n            this.RenderThreadsControl.Size = new System.Drawing.Size(63, 23);\r\n            this.RenderThreadsControl.TabIndex = 11;\r\n            this.RenderThreadsControl.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;\r\n            this.RenderThreadsControl.Value = new decimal(new int[] {\r\n            4,\r\n            0,\r\n            0,\r\n            0});\r\n            this.RenderThreadsControl.ValueChanged += new System.EventHandler(this.ControlValueChanged);\r\n            // \r\n            // label2\r\n            // \r\n            this.label2.AutoSize = true;\r\n            this.label2.Location = new System.Drawing.Point(14, 267);\r\n            this.label2.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);\r\n            this.label2.Name = \"label2\";\r\n            this.label2.Size = new System.Drawing.Size(86, 15);\r\n            this.label2.TabIndex = 10;\r\n            this.label2.Text = \"Render threads\";\r\n            // \r\n            // groupBox4\r\n            // \r\n            this.groupBox4.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \r\n            | System.Windows.Forms.AnchorStyles.Right)));\r\n            this.groupBox4.Controls.Add(this.PreviewCheckBox);\r\n            this.groupBox4.Controls.Add(this.label10);\r\n            this.groupBox4.Controls.Add(this.PreviewFrameskip);\r\n            this.groupBox4.Controls.Add(this.label13);\r\n            this.groupBox4.Location = new System.Drawing.Point(6, 36);\r\n            this.groupBox4.Name = \"groupBox4\";\r\n            this.groupBox4.Size = new System.Drawing.Size(421, 80);\r\n            this.groupBox4.TabIndex = 9;\r\n            this.groupBox4.TabStop = false;\r\n            this.groupBox4.Text = \"Preview\";\r\n            // \r\n            // PreviewCheckBox\r\n            // \r\n            this.PreviewCheckBox.AutoSize = true;\r\n            this.PreviewCheckBox.Location = new System.Drawing.Point(11, 23);\r\n            this.PreviewCheckBox.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);\r\n            this.PreviewCheckBox.Name = \"PreviewCheckBox\";\r\n            this.PreviewCheckBox.Size = new System.Drawing.Size(68, 19);\r\n            this.PreviewCheckBox.TabIndex = 3;\r\n            this.PreviewCheckBox.Text = \"Enabled\";\r\n            this.PreviewCheckBox.UseVisualStyleBackColor = true;\r\n            this.PreviewCheckBox.CheckedChanged += new System.EventHandler(this.ControlValueChanged);\r\n            // \r\n            // label10\r\n            // \r\n            this.label10.AutoSize = true;\r\n            this.label10.Location = new System.Drawing.Point(8, 52);\r\n            this.label10.Name = \"label10\";\r\n            this.label10.Size = new System.Drawing.Size(89, 15);\r\n            this.label10.TabIndex = 4;\r\n            this.label10.Text = \"Show 1 in every\";\r\n            // \r\n            // PreviewFrameskip\r\n            // \r\n            this.PreviewFrameskip.Location = new System.Drawing.Point(112, 50);\r\n            this.PreviewFrameskip.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);\r\n            this.PreviewFrameskip.Name = \"PreviewFrameskip\";\r\n            this.PreviewFrameskip.Size = new System.Drawing.Size(63, 23);\r\n            this.PreviewFrameskip.TabIndex = 5;\r\n            this.PreviewFrameskip.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;\r\n            this.PreviewFrameskip.Value = new decimal(new int[] {\r\n            60,\r\n            0,\r\n            0,\r\n            0});\r\n            this.PreviewFrameskip.ValueChanged += new System.EventHandler(this.ControlValueChanged);\r\n            // \r\n            // label13\r\n            // \r\n            this.label13.AutoSize = true;\r\n            this.label13.Location = new System.Drawing.Point(181, 52);\r\n            this.label13.Name = \"label13\";\r\n            this.label13.Size = new System.Drawing.Size(43, 15);\r\n            this.label13.TabIndex = 6;\r\n            this.label13.Text = \"frames\";\r\n            // \r\n            // groupBox3\r\n            // \r\n            this.groupBox3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \r\n            | System.Windows.Forms.AnchorStyles.Right)));\r\n            this.groupBox3.Controls.Add(this.ExtraFFMPEGParameters);\r\n            this.groupBox3.Controls.Add(this.label22);\r\n            this.groupBox3.Controls.Add(this.AudioCodec);\r\n            this.groupBox3.Controls.Add(this.label18);\r\n            this.groupBox3.Controls.Add(this.VideoCodec);\r\n            this.groupBox3.Controls.Add(this.label3);\r\n            this.groupBox3.Controls.Add(this.EncodeCheckBox);\r\n            this.groupBox3.Location = new System.Drawing.Point(6, 123);\r\n            this.groupBox3.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);\r\n            this.groupBox3.Name = \"groupBox3\";\r\n            this.groupBox3.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4);\r\n            this.groupBox3.Size = new System.Drawing.Size(421, 135);\r\n            this.groupBox3.TabIndex = 7;\r\n            this.groupBox3.TabStop = false;\r\n            this.groupBox3.Text = \"FFMPEG\";\r\n            // \r\n            // ExtraFFMPEGParameters\r\n            // \r\n            this.ExtraFFMPEGParameters.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \r\n            | System.Windows.Forms.AnchorStyles.Right)));\r\n            this.ExtraFFMPEGParameters.Location = new System.Drawing.Point(112, 102);\r\n            this.ExtraFFMPEGParameters.Name = \"ExtraFFMPEGParameters\";\r\n            this.ExtraFFMPEGParameters.Size = new System.Drawing.Size(303, 23);\r\n            this.ExtraFFMPEGParameters.TabIndex = 10;\r\n            // \r\n            // label22\r\n            // \r\n            this.label22.AutoSize = true;\r\n            this.label22.Location = new System.Drawing.Point(8, 105);\r\n            this.label22.Name = \"label22\";\r\n            this.label22.Size = new System.Drawing.Size(95, 15);\r\n            this.label22.TabIndex = 9;\r\n            this.label22.Text = \"Extra parameters\";\r\n            // \r\n            // AudioCodec\r\n            // \r\n            this.AudioCodec.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \r\n            | System.Windows.Forms.AnchorStyles.Right)));\r\n            this.AudioCodec.Location = new System.Drawing.Point(112, 73);\r\n            this.AudioCodec.Name = \"AudioCodec\";\r\n            this.AudioCodec.Size = new System.Drawing.Size(303, 23);\r\n            this.AudioCodec.TabIndex = 8;\r\n            this.AudioCodec.Text = \"aac\";\r\n            this.AudioCodec.DropDown += new System.EventHandler(this.VideoCodec_DropDown);\r\n            this.AudioCodec.TextChanged += new System.EventHandler(this.ControlValueChanged);\r\n            // \r\n            // label18\r\n            // \r\n            this.label18.AutoSize = true;\r\n            this.label18.Location = new System.Drawing.Point(8, 76);\r\n            this.label18.Name = \"label18\";\r\n            this.label18.Size = new System.Drawing.Size(74, 15);\r\n            this.label18.TabIndex = 7;\r\n            this.label18.Text = \"Audio codec\";\r\n            // \r\n            // VideoCodec\r\n            // \r\n            this.VideoCodec.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \r\n            | System.Windows.Forms.AnchorStyles.Right)));\r\n            this.VideoCodec.Location = new System.Drawing.Point(112, 44);\r\n            this.VideoCodec.Name = \"VideoCodec\";\r\n            this.VideoCodec.Size = new System.Drawing.Size(303, 23);\r\n            this.VideoCodec.TabIndex = 6;\r\n            this.VideoCodec.Text = \"libx264\";\r\n            this.VideoCodec.DropDown += new System.EventHandler(this.VideoCodec_DropDown);\r\n            this.VideoCodec.TextChanged += new System.EventHandler(this.ControlValueChanged);\r\n            // \r\n            // label3\r\n            // \r\n            this.label3.AutoSize = true;\r\n            this.label3.Location = new System.Drawing.Point(8, 47);\r\n            this.label3.Name = \"label3\";\r\n            this.label3.Size = new System.Drawing.Size(72, 15);\r\n            this.label3.TabIndex = 5;\r\n            this.label3.Text = \"Video codec\";\r\n            // \r\n            // EncodeCheckBox\r\n            // \r\n            this.EncodeCheckBox.AutoSize = true;\r\n            this.EncodeCheckBox.Location = new System.Drawing.Point(11, 24);\r\n            this.EncodeCheckBox.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);\r\n            this.EncodeCheckBox.Name = \"EncodeCheckBox\";\r\n            this.EncodeCheckBox.Size = new System.Drawing.Size(98, 19);\r\n            this.EncodeCheckBox.TabIndex = 0;\r\n            this.EncodeCheckBox.Text = \"Encode to file\";\r\n            this.EncodeCheckBox.UseVisualStyleBackColor = true;\r\n            this.EncodeCheckBox.CheckedChanged += new System.EventHandler(this.ControlValueChanged);\r\n            // \r\n            // FrameRateControl\r\n            // \r\n            this.FrameRateControl.Location = new System.Drawing.Point(118, 7);\r\n            this.FrameRateControl.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.FrameRateControl.Maximum = new decimal(new int[] {\r\n            1000,\r\n            0,\r\n            0,\r\n            0});\r\n            this.FrameRateControl.Minimum = new decimal(new int[] {\r\n            1,\r\n            0,\r\n            0,\r\n            0});\r\n            this.FrameRateControl.Name = \"FrameRateControl\";\r\n            this.FrameRateControl.Size = new System.Drawing.Size(63, 23);\r\n            this.FrameRateControl.TabIndex = 1;\r\n            this.FrameRateControl.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;\r\n            this.FrameRateControl.Value = new decimal(new int[] {\r\n            60,\r\n            0,\r\n            0,\r\n            0});\r\n            this.FrameRateControl.ValueChanged += new System.EventHandler(this.ControlValueChanged);\r\n            // \r\n            // label14\r\n            // \r\n            this.label14.AutoSize = true;\r\n            this.label14.Location = new System.Drawing.Point(185, 9);\r\n            this.label14.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);\r\n            this.label14.Name = \"label14\";\r\n            this.label14.Size = new System.Drawing.Size(23, 15);\r\n            this.label14.TabIndex = 2;\r\n            this.label14.Text = \"fps\";\r\n            // \r\n            // label15\r\n            // \r\n            this.label15.AutoSize = true;\r\n            this.label15.Location = new System.Drawing.Point(14, 9);\r\n            this.label15.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);\r\n            this.label15.Name = \"label15\";\r\n            this.label15.Size = new System.Drawing.Size(63, 15);\r\n            this.label15.TabIndex = 0;\r\n            this.label15.Text = \"Frame rate\";\r\n            // \r\n            // RenderButton\r\n            // \r\n            this.RenderButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \r\n            | System.Windows.Forms.AnchorStyles.Right)));\r\n            this.RenderButton.Location = new System.Drawing.Point(6, 295);\r\n            this.RenderButton.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);\r\n            this.RenderButton.Name = \"RenderButton\";\r\n            this.RenderButton.Size = new System.Drawing.Size(421, 61);\r\n            this.RenderButton.TabIndex = 8;\r\n            this.RenderButton.Text = \"Render\";\r\n            this.RenderButton.UseVisualStyleBackColor = true;\r\n            this.RenderButton.Click += new System.EventHandler(this.RenderButton_Click);\r\n            // \r\n            // tabPage6\r\n            // \r\n            this.tabPage6.Controls.Add(this.LoadButton);\r\n            this.tabPage6.Controls.Add(this.SaveButton);\r\n            this.tabPage6.ImageKey = \"script.png\";\r\n            this.tabPage6.Location = new System.Drawing.Point(4, 44);\r\n            this.tabPage6.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);\r\n            this.tabPage6.Name = \"tabPage6\";\r\n            this.tabPage6.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4);\r\n            this.tabPage6.Size = new System.Drawing.Size(433, 600);\r\n            this.tabPage6.TabIndex = 6;\r\n            this.tabPage6.Text = \"Settings\";\r\n            this.tabPage6.UseVisualStyleBackColor = true;\r\n            // \r\n            // LoadButton\r\n            // \r\n            this.LoadButton.Image = ((System.Drawing.Image)(resources.GetObject(\"LoadButton.Image\")));\r\n            this.LoadButton.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft;\r\n            this.LoadButton.Location = new System.Drawing.Point(128, 8);\r\n            this.LoadButton.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);\r\n            this.LoadButton.Name = \"LoadButton\";\r\n            this.LoadButton.Size = new System.Drawing.Size(115, 39);\r\n            this.LoadButton.TabIndex = 1;\r\n            this.LoadButton.Text = \"Load\";\r\n            this.LoadButton.UseVisualStyleBackColor = true;\r\n            this.LoadButton.Click += new System.EventHandler(this.LoadProject);\r\n            // \r\n            // SaveButton\r\n            // \r\n            this.SaveButton.Image = ((System.Drawing.Image)(resources.GetObject(\"SaveButton.Image\")));\r\n            this.SaveButton.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft;\r\n            this.SaveButton.Location = new System.Drawing.Point(7, 8);\r\n            this.SaveButton.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);\r\n            this.SaveButton.Name = \"SaveButton\";\r\n            this.SaveButton.Size = new System.Drawing.Size(115, 39);\r\n            this.SaveButton.TabIndex = 0;\r\n            this.SaveButton.Text = \"Save\";\r\n            this.SaveButton.UseVisualStyleBackColor = true;\r\n            this.SaveButton.Click += new System.EventHandler(this.SaveProject);\r\n            // \r\n            // tabPage5\r\n            // \r\n            this.tabPage5.Controls.Add(this.ProgramSettingsGrid);\r\n            this.tabPage5.ImageKey = \"application_xp_terminal.png\";\r\n            this.tabPage5.Location = new System.Drawing.Point(4, 44);\r\n            this.tabPage5.Name = \"tabPage5\";\r\n            this.tabPage5.Padding = new System.Windows.Forms.Padding(3);\r\n            this.tabPage5.Size = new System.Drawing.Size(433, 600);\r\n            this.tabPage5.TabIndex = 7;\r\n            this.tabPage5.Text = \"Programs\";\r\n            this.tabPage5.UseVisualStyleBackColor = true;\r\n            // \r\n            // ProgramSettingsGrid\r\n            // \r\n            this.ProgramSettingsGrid.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \r\n            | System.Windows.Forms.AnchorStyles.Left) \r\n            | System.Windows.Forms.AnchorStyles.Right)));\r\n            this.ProgramSettingsGrid.Location = new System.Drawing.Point(6, 6);\r\n            this.ProgramSettingsGrid.Name = \"ProgramSettingsGrid\";\r\n            this.ProgramSettingsGrid.Size = new System.Drawing.Size(421, 588);\r\n            this.ProgramSettingsGrid.TabIndex = 0;\r\n            this.ProgramSettingsGrid.ToolbarVisible = false;\r\n            // \r\n            // imageList1\r\n            // \r\n            this.imageList1.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject(\"imageList1.ImageStream\")));\r\n            this.imageList1.TransparentColor = System.Drawing.Color.Transparent;\r\n            this.imageList1.Images.SetKeyName(0, \"sound.png\");\r\n            this.imageList1.Images.SetKeyName(1, \"film.png\");\r\n            this.imageList1.Images.SetKeyName(2, \"script.png\");\r\n            this.imageList1.Images.SetKeyName(3, \"music.png\");\r\n            this.imageList1.Images.SetKeyName(4, \"layout.png\");\r\n            this.imageList1.Images.SetKeyName(5, \"picture.png\");\r\n            this.imageList1.Images.SetKeyName(6, \"application_xp_terminal.png\");\r\n            // \r\n            // BackgroundColorButton\r\n            // \r\n            this.BackgroundColorButton.BackColor = System.Drawing.Color.Black;\r\n            this.BackgroundColorButton.Color = System.Drawing.Color.Black;\r\n            this.BackgroundColorButton.ForeColor = System.Drawing.Color.White;\r\n            this.BackgroundColorButton.Location = new System.Drawing.Point(118, 6);\r\n            this.BackgroundColorButton.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.BackgroundColorButton.Name = \"BackgroundColorButton\";\r\n            this.BackgroundColorButton.Size = new System.Drawing.Size(114, 33);\r\n            this.BackgroundColorButton.TabIndex = 1;\r\n            this.BackgroundColorButton.Text = \"Black\";\r\n            this.BackgroundColorButton.UseVisualStyleBackColor = false;\r\n            this.BackgroundColorButton.ColorChanged += new System.EventHandler(this.ControlValueChanged);\r\n            // \r\n            // SidWizPlusGui\r\n            // \r\n            this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);\r\n            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;\r\n            this.ClientSize = new System.Drawing.Size(1140, 648);\r\n            this.Controls.Add(this.splitContainer1);\r\n            this.Font = new System.Drawing.Font(\"Segoe UI\", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));\r\n            this.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);\r\n            this.Name = \"SidWizPlusGui\";\r\n            this.Text = \"SidWizPlusGUI\";\r\n            this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.HandleClosing);\r\n            this.Load += new System.EventHandler(this.Initialize);\r\n            this.splitContainer1.Panel1.ResumeLayout(false);\r\n            this.splitContainer1.Panel1.PerformLayout();\r\n            this.splitContainer1.Panel2.ResumeLayout(false);\r\n            ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit();\r\n            this.splitContainer1.ResumeLayout(false);\r\n            ((System.ComponentModel.ISupportInitialize)(this.Preview)).EndInit();\r\n            this.contextMenuStrip1.ResumeLayout(false);\r\n            ((System.ComponentModel.ISupportInitialize)(this.PreviewTrackbar)).EndInit();\r\n            this.tabControl.ResumeLayout(false);\r\n            this.channelsTab.ResumeLayout(false);\r\n            this.channelsTab.PerformLayout();\r\n            this.toolStrip1.ResumeLayout(false);\r\n            this.toolStrip1.PerformLayout();\r\n            this.tabPage1.ResumeLayout(false);\r\n            this.tabPage1.PerformLayout();\r\n            this.groupBox2.ResumeLayout(false);\r\n            this.groupBox2.PerformLayout();\r\n            ((System.ComponentModel.ISupportInitialize)(this.MarginBottomControl)).EndInit();\r\n            ((System.ComponentModel.ISupportInitialize)(this.MarginRightControl)).EndInit();\r\n            ((System.ComponentModel.ISupportInitialize)(this.MarginLeftControl)).EndInit();\r\n            ((System.ComponentModel.ISupportInitialize)(this.MarginTopControl)).EndInit();\r\n            ((System.ComponentModel.ISupportInitialize)(this.Columns)).EndInit();\r\n            this.tabPage4.ResumeLayout(false);\r\n            this.tabPage4.PerformLayout();\r\n            ((System.ComponentModel.ISupportInitialize)(this.BackgroundImageControl)).EndInit();\r\n            this.tabPage3.ResumeLayout(false);\r\n            this.tabPage3.PerformLayout();\r\n            this.tabPage2.ResumeLayout(false);\r\n            this.tabPage2.PerformLayout();\r\n            ((System.ComponentModel.ISupportInitialize)(this.RenderThreadsControl)).EndInit();\r\n            this.groupBox4.ResumeLayout(false);\r\n            this.groupBox4.PerformLayout();\r\n            ((System.ComponentModel.ISupportInitialize)(this.PreviewFrameskip)).EndInit();\r\n            this.groupBox3.ResumeLayout(false);\r\n            this.groupBox3.PerformLayout();\r\n            ((System.ComponentModel.ISupportInitialize)(this.FrameRateControl)).EndInit();\r\n            this.tabPage6.ResumeLayout(false);\r\n            this.tabPage5.ResumeLayout(false);\r\n            this.ResumeLayout(false);\r\n\r\n        }\r\n\r\n        #endregion\r\n        private System.Windows.Forms.NumericUpDown Columns;\r\n        private System.Windows.Forms.Label label1;\r\n        private System.Windows.Forms.TextBox HeightControl;\r\n        private System.Windows.Forms.Label label5;\r\n        private System.Windows.Forms.TextBox WidthControl;\r\n        private System.Windows.Forms.Label label4;\r\n        private System.Windows.Forms.CheckBox MasterMixReplayGain;\r\n        private System.Windows.Forms.CheckBox AutogenerateMasterMix;\r\n        private System.Windows.Forms.Label label12;\r\n        private System.Windows.Forms.Label label11;\r\n        private System.Windows.Forms.Label label16;\r\n        private System.Windows.Forms.Label label17;\r\n        private System.Windows.Forms.TextBox VerticalScaling;\r\n        private System.Windows.Forms.TabControl tabControl;\r\n        private System.Windows.Forms.TabPage tabPage1;\r\n        private System.Windows.Forms.TabPage tabPage3;\r\n        private System.Windows.Forms.TabPage tabPage4;\r\n        private System.Windows.Forms.Label label21;\r\n        private ColorButton BackgroundColorButton;\r\n        private System.Windows.Forms.Label label20;\r\n        private System.Windows.Forms.PictureBox BackgroundImageControl;\r\n        private System.Windows.Forms.Button AutoScaleButton;\r\n        private System.Windows.Forms.PropertyGrid PropertyGrid;\r\n        private System.Windows.Forms.SplitContainer splitContainer1;\r\n        private System.Windows.Forms.ToolStrip toolStrip1;\r\n        private System.Windows.Forms.PictureBox Preview;\r\n        private System.Windows.Forms.TabPage channelsTab;\r\n        private System.Windows.Forms.ToolStripButton AddFileButton;\r\n        private System.Windows.Forms.ToolStripSeparator ChannelToolstripItemsSeparator;\r\n        private System.Windows.Forms.GroupBox groupBox2;\r\n        private System.Windows.Forms.Label label9;\r\n        private System.Windows.Forms.Label label8;\r\n        private System.Windows.Forms.Label label7;\r\n        private System.Windows.Forms.Label label6;\r\n        private System.Windows.Forms.NumericUpDown MarginBottomControl;\r\n        private System.Windows.Forms.NumericUpDown MarginRightControl;\r\n        private System.Windows.Forms.NumericUpDown MarginLeftControl;\r\n        private System.Windows.Forms.NumericUpDown MarginTopControl;\r\n        private System.Windows.Forms.TabPage tabPage2;\r\n        private System.Windows.Forms.Label label13;\r\n        private System.Windows.Forms.NumericUpDown PreviewFrameskip;\r\n        private System.Windows.Forms.Label label10;\r\n        private System.Windows.Forms.CheckBox PreviewCheckBox;\r\n        private System.Windows.Forms.ContextMenuStrip contextMenuStrip1;\r\n        private System.Windows.Forms.ToolStripMenuItem moveLeftupToolStripMenuItem;\r\n        private System.Windows.Forms.ToolStripMenuItem moveRightdownToolStripMenuItem;\r\n        private System.Windows.Forms.ToolStripSeparator toolStripMenuItem1;\r\n        private System.Windows.Forms.ToolStripMenuItem removeChannelToolStripMenuItem;\r\n        private System.Windows.Forms.ToolStripSeparator toolStripSeparator3;\r\n        private System.Windows.Forms.ToolStripMenuItem applySettingsToOtherChannelsToolStripMenuItem;\r\n        private System.Windows.Forms.ImageList imageList1;\r\n        private System.Windows.Forms.GroupBox groupBox3;\r\n        private System.Windows.Forms.CheckBox EncodeCheckBox;\r\n        private System.Windows.Forms.NumericUpDown FrameRateControl;\r\n        private System.Windows.Forms.Label label14;\r\n        private System.Windows.Forms.Label label15;\r\n        private System.Windows.Forms.Button RenderButton;\r\n        private System.Windows.Forms.TrackBar PreviewTrackbar;\r\n        private System.Windows.Forms.Label label19;\r\n        private System.Windows.Forms.TextBox MasterAudioPath;\r\n        private System.Windows.Forms.ToolStripDropDownButton RemoveButton;\r\n        private System.Windows.Forms.ToolStripMenuItem removeselectedToolStripMenuItem;\r\n        private System.Windows.Forms.ToolStripMenuItem removeemptyToolStripMenuItem;\r\n        private System.Windows.Forms.ToolStripSeparator toolStripMenuItem2;\r\n        private System.Windows.Forms.ToolStripMenuItem removeallToolStripMenuItem;\r\n        private System.Windows.Forms.TabPage tabPage6;\r\n        private System.Windows.Forms.Button SaveButton;\r\n        private System.Windows.Forms.Button LoadButton;\r\n        private System.Windows.Forms.GroupBox groupBox4;\r\n        private System.Windows.Forms.ToolStripMenuItem cloneChannelToolStripMenuItem;\r\n        private System.Windows.Forms.ToolStripMenuItem copySettingsToolStripMenuItem;\r\n        private System.Windows.Forms.ToolStripMenuItem pasteSettingsToolStripMenuItem;\r\n        private System.Windows.Forms.ToolStripMenuItem splitChannelToolStripMenuItem;\r\n        private System.Windows.Forms.ToolStripButton RemoveLabelsButton;\r\n        private System.Windows.Forms.TabPage tabPage5;\r\n        private System.Windows.Forms.PropertyGrid ProgramSettingsGrid;\r\n        private System.Windows.Forms.ToolStripMenuItem setAsdefaultsToolStripMenuItem;\r\n        private System.Windows.Forms.ToolStripSeparator toolStripSeparator5;\r\n        private System.Windows.Forms.ToolStripMenuItem resetToDefaultSettingsToolStripMenuItem;\r\n        private System.Windows.Forms.ToolStripButton AddChannelButton;\r\n        private System.Windows.Forms.Label ChannelsHelpLabel;\r\n        private System.Windows.Forms.NumericUpDown RenderThreadsControl;\r\n        private System.Windows.Forms.Label label2;\r\n        private System.Windows.Forms.Label label3;\r\n        private ComboBox AudioCodec;\r\n        private System.Windows.Forms.Label label18;\r\n        private ComboBox VideoCodec;\r\n        private TextBox ExtraFFMPEGParameters;\r\n        private Label label22;\r\n    }\r\n}"
  },
  {
    "path": "SidWiz/SidWizPlusGUI.cs",
    "content": "﻿using System;\r\nusing System.Collections.Generic;\r\nusing System.ComponentModel;\r\nusing System.Diagnostics;\r\nusing System.Diagnostics.CodeAnalysis;\r\nusing System.Drawing;\r\nusing System.Drawing.Design;\r\nusing System.Globalization;\r\nusing System.IO;\r\nusing System.Linq;\r\nusing System.Reflection;\r\nusing System.Text.RegularExpressions;\r\nusing System.Threading.Tasks;\r\nusing System.Windows.Forms;\r\nusing System.Windows.Forms.Design;\r\nusing LibSidWiz;\r\nusing LibSidWiz.Outputs;\r\nusing LibSidWiz.Triggers;\r\nusing Microsoft.WindowsAPICodePack.Dialogs;\r\nusing Newtonsoft.Json;\r\nusing SkiaSharp;\r\n\r\nnamespace SidWizPlusGUI\r\n{\r\n    public partial class SidWizPlusGui : Form\r\n    {\r\n        [SuppressMessage(\"ReSharper\", \"UnusedAutoPropertyAccessor.Global\")]\r\n        [SuppressMessage(\"ReSharper\", \"AutoPropertyCanBeMadeGetOnly.Global\")]\r\n        public class ProgramSettings\r\n        {\r\n            [Category(\"FFMPEG\")]\r\n            [Editor(typeof(FileNameEditor), typeof(UITypeEditor))]\r\n            [DisplayName(\"Path\")]\r\n            [Description(\"Path to FFMPEG. Download from https://ffmpeg.org/download.html\")]\r\n            public string FfmpegPath { get; set; }\r\n            [Category(\"FFMPEG\")]\r\n            [DisplayName(\"Global Extra Parameters\")]\r\n            [Description(\"Extra parameters for FFMPEG. These are stored in your user settings, and will apply to all encodes. Per-encode settings can be entered on the Video tab.\")]\r\n            public string FfmpegExtraParameters { get; set; }\r\n\r\n            [Category(\"MultiDumper\")]\r\n            [Editor(typeof(FileNameEditor), typeof(UITypeEditor))]\r\n            [DisplayName(\"Path\")]\r\n            [Description(\"Path to Multidumper. Download from https://github.com/maxim-zhao/multidumper/releases\")]\r\n            public string MultiDumperPath { get; set; }\r\n            [Category(\"MultiDumper\")]\r\n            [DisplayName(\"Sampling Rate\")]\r\n            [Description(\"Sampling rate for generated WAV files. Default is 44100. Requires a build of MultiDumper that supports this!\")]\r\n            [DefaultValue(44100)]\r\n            public int MultiDumperSamplingRate { get; set; } = 44100;\r\n            [Category(\"MultiDumper\")]\r\n            [DisplayName(\"Loop Count\")]\r\n            [Description(\"Default is 2 (so looped sections will be played twice in their entirety). Requires a build of MultiDumper that supports this!\")]\r\n            [DefaultValue(2)]\r\n            public int MultiDumperLoopCount { get; set; } = 2;\r\n            [Category(\"MultiDumper\")]\r\n            [DisplayName(\"Fade out time (ms)\")]\r\n            [Description(\"Default is 8000 (8s). Requires a build of MultiDumper that supports this!\")]\r\n            [DefaultValue(8000)]\r\n            public int MultiDumperFadeMs { get; set; } = 8000;\r\n            [Category(\"MultiDumper\")]\r\n            [DisplayName(\"Gap time (ms)\")]\r\n            [Description(\"Default is 1000 (1s). Requires a build of MultiDumper that supports this!\")]\r\n            [DefaultValue(1000)]\r\n            public int MultiDumperGapMs { get; set; } = 1000;\r\n            [Category(\"MultiDumper\")]\r\n            [DisplayName(\"Extra settings\")]\r\n            [Description(\"Extra settings to pass to MultiDumper\")]\r\n            [DefaultValue(\"\")]\r\n            public string MultiDumperExtraSettings { get; set; } = \"\";\r\n\r\n            [Category(\"SidPlay\")]\r\n            [Editor(typeof(FileNameEditor), typeof(UITypeEditor))]\r\n            [DisplayName(\"Path\")]\r\n            public string SidPlayPath { get; set; }\r\n\r\n            [Browsable(false)]\r\n            public Channel DefaultChannelSettings { get; set; } = new(true)\r\n            {\r\n                Algorithm = new PeakSpeedTrigger(),\r\n                LabelColor = Color.White,\r\n                LabelFont = new Font(DefaultFont, FontStyle.Regular)\r\n            };\r\n        }\r\n\r\n        private ProgramSettings _programSettings = new();\r\n\r\n        // ReSharper disable MemberCanBePrivate.Local\r\n        private class Settings\r\n        {\r\n            private bool _ignoreFromControls;\r\n            public int Columns { get; set; } = 1;\r\n            public List<Channel> Channels { get; } = [];\r\n            public float AutoScaleHeight { get; set; } = 100;\r\n            public int Width { get; set; } = 1280;\r\n            public int Height { get; set; } = 720;\r\n            public int MarginTop { get; set; }\r\n            public int MarginLeft { get; set; }\r\n            public int MarginRight { get; set; }\r\n            public int MarginBottom { get; set; }\r\n            public int FrameRate { get; set; } = 60;\r\n            public Color BackgroundColor { get; set; } = Color.Black;\r\n            public string BackgroundImageFilename { get; set; }\r\n            public PreviewSettings Preview { get; } = new() {Enabled = true, Frameskip = 1};\r\n            public EncodeSettings EncodeVideo { get; } = new() {Enabled = false, VideoCodec = \"libx264\", AudioCodec = \"aac\", ExtraParameters = \"\"};\r\n            public int RenderThreads { get; set; } = Environment.ProcessorCount;\r\n\r\n            public MasterAudioSettings MasterAudio { get; } = new() {IsAutomatic = true, ApplyReplayGain = true};\r\n\r\n            public class MasterAudioSettings\r\n            {\r\n                public bool IsAutomatic { get; set; }\r\n                public bool ApplyReplayGain { get; set; }\r\n                public string Path { get; set; }\r\n            }\r\n\r\n            public class EncodeSettings\r\n            {\r\n                public bool Enabled { get; set; }\r\n                public string VideoCodec { get; set; }\r\n                public string AudioCodec { get; set; }\r\n                public string ExtraParameters { get; set; }\r\n            }\r\n\r\n            public class PreviewSettings\r\n            {\r\n                public bool Enabled { get; set; }\r\n                public int Frameskip { get; set; }\r\n            }\r\n\r\n            public void FromControls(SidWizPlusGui form)\r\n            {\r\n                if (_ignoreFromControls)\r\n                {\r\n                    return;\r\n                }\r\n\r\n                AutoScaleHeight = float.Parse(form.VerticalScaling.Text);\r\n                Width = int.Parse(form.WidthControl.Text);\r\n                Height = int.Parse(form.HeightControl.Text);\r\n                Columns = (int) form.Columns.Value;\r\n                MarginTop = (int) form.MarginTopControl.Value;\r\n                MarginLeft = (int) form.MarginLeftControl.Value;\r\n                MarginRight = (int) form.MarginRightControl.Value;\r\n                MarginBottom = (int) form.MarginBottomControl.Value;\r\n                FrameRate = (int) form.FrameRateControl.Value;\r\n                BackgroundColor = form.BackgroundColorButton.Color;\r\n                Preview.Enabled = form.PreviewCheckBox.Checked;\r\n                Preview.Frameskip = (int) form.PreviewFrameskip.Value;\r\n                EncodeVideo.Enabled = form.EncodeCheckBox.Checked;\r\n                EncodeVideo.VideoCodec = form.VideoCodec.Text.Split([' '], 2).FirstOrDefault() ?? \"libx264\";\r\n                EncodeVideo.AudioCodec = form.AudioCodec.Text.Split([' '], 2).FirstOrDefault() ?? \"aac\";\r\n                EncodeVideo.ExtraParameters = form.ExtraFFMPEGParameters.Text;\r\n                MasterAudio.IsAutomatic = form.AutogenerateMasterMix.Checked;\r\n                MasterAudio.ApplyReplayGain = form.MasterMixReplayGain.Checked;\r\n                MasterAudio.Path = form.MasterAudioPath.Text;\r\n                RenderThreads = (int) form.RenderThreadsControl.Value;\r\n            }\r\n\r\n            public void ToControls(SidWizPlusGui form)\r\n            {\r\n                // Disable control notifications as we load values into them\r\n                _ignoreFromControls = true;\r\n                form.VerticalScaling.Text = AutoScaleHeight.ToString(CultureInfo.CurrentCulture);\r\n                form.WidthControl.Text = Width.ToString();\r\n                form.HeightControl.Text = Height.ToString();\r\n                form.Columns.Value = Columns;\r\n                form.MarginTopControl.Value = MarginTop;\r\n                form.MarginLeftControl.Value = MarginLeft;\r\n                form.MarginRightControl.Value = MarginRight;\r\n                form.MarginBottomControl.Value = MarginBottom;\r\n                form.FrameRateControl.Value = FrameRate;\r\n                form.BackgroundColorButton.Color = BackgroundColor;\r\n                form.PreviewCheckBox.Checked = Preview.Enabled;\r\n                form.PreviewFrameskip.Value = Preview.Frameskip;\r\n                form.EncodeCheckBox.Checked = EncodeVideo.Enabled;\r\n                form.VideoCodec.Text = EncodeVideo.VideoCodec;\r\n                form.AudioCodec.Text = EncodeVideo.AudioCodec;\r\n                form.ExtraFFMPEGParameters.Text = EncodeVideo.ExtraParameters;\r\n                form.AutogenerateMasterMix.Checked = MasterAudio.IsAutomatic;\r\n                form.MasterMixReplayGain.Checked = MasterAudio.ApplyReplayGain;\r\n                form.MasterAudioPath.Text = MasterAudio.Path;\r\n                form.RenderThreadsControl.Value = RenderThreads;\r\n                _ignoreFromControls = false;\r\n            }\r\n\r\n            public Rectangle GetBounds()\r\n            {\r\n                return new Rectangle(\r\n                    MarginLeft,\r\n                    MarginTop,\r\n                    Width - MarginLeft - MarginRight,\r\n                    Height - MarginTop - MarginBottom);\r\n            }\r\n        }\r\n        // ReSharper restore MemberCanBePrivate.Local\r\n\r\n        private readonly Settings _settings = new();\r\n\r\n        // We do rendering on a worker thread, this means we have some complicated interactions\r\n        // to make sure it doesn't render more or less than needed. This lot deals with that.\r\n        private readonly object _renderLock = new();\r\n        private bool _renderNeeded;\r\n        private bool _renderActive;\r\n        private float _renderPosition;\r\n\r\n        // We use this to allow cancelling the render\r\n        private MainFormProgressOutput _progress;\r\n        private FileSystemWatcher _settingsWatcher;\r\n\r\n        public SidWizPlusGui()\r\n        {\r\n            InitializeComponent();\r\n        }\r\n\r\n        private class FileTypeHandler\r\n        {\r\n            private readonly Action<string> _handler;\r\n            private readonly HashSet<string> _extensions;\r\n\r\n            public FileTypeHandler(string name, Action<string> handler, params string[] extensions)\r\n            {\r\n                Name = name;\r\n                _handler = handler;\r\n                _extensions = [..extensions];\r\n                Filter = \"*.\" + string.Join(\"; *.\", extensions);\r\n            }\r\n\r\n            public string Name { get; }\r\n            public string Filter { get; }\r\n\r\n            public bool TryHandle(string filename)\r\n            {\r\n                var extension = Path.GetExtension(filename)?.ToLowerInvariant();\r\n                if (extension == null)\r\n                {\r\n                    return false;\r\n                }\r\n\r\n                if (extension.StartsWith(\".\"))\r\n                {\r\n                    extension = extension.Substring(1);\r\n                }\r\n\r\n                if (_extensions.Contains(extension))\r\n                {\r\n                    _handler(filename);\r\n                    return true;\r\n                }\r\n\r\n                return false;\r\n            }\r\n        }\r\n\r\n        private void AddAFileClick(object sender, EventArgs e)\r\n        {\r\n            var handlers = new[]\r\n            {\r\n                // ReSharper disable once StringLiteralTypo\r\n                new FileTypeHandler(\"Multidumper compatible files\", LoadMultiDumper, \"ay\", \"gbs\", \"gym\", \"hes\", \"kss\",\r\n                    \"nsf\", \"nsfe\", \"sap\", \"sfm\", \"sgc\", \"spc\", \"vgm\", \"vgz\", \"spu\"),\r\n                new FileTypeHandler(\"Wave audio files\", AddChannel, \"wav\", \"mp3\"),\r\n                new FileTypeHandler(\"SID files\", LoadSid, \"sid\")\r\n            };\r\n\r\n            var allFilesMask = string.Join(\"; \", handlers.Select(h => h.Filter));\r\n\r\n            var filter = string.Join(\"|\", new[]\r\n                {\r\n                    $\"All supported files ({allFilesMask})\",\r\n                    allFilesMask\r\n                }\r\n                .Concat(handlers.SelectMany(h => new[] {$\"{h.Name} ({h.Filter})\", h.Filter}))\r\n                .Concat(\r\n                [\r\n                    \"All files\", \"*.*\"\r\n                ]));\r\n\r\n            using var ofd = new OpenFileDialog();\r\n            ofd.CheckFileExists = true;\r\n            ofd.Filter = filter;\r\n            ofd.Multiselect = true;\r\n            if (ofd.ShowDialog(this) != DialogResult.OK)\r\n            {\r\n                return;\r\n            }\r\n\r\n            var errors = new List<string>();\r\n            foreach (var filename in ofd.FileNames.OrderByAlphaNumeric(x => x))\r\n            {\r\n                var path = Path.GetFullPath(filename);\r\n                if (!handlers.Any(h => h.TryHandle(path)))\r\n                {\r\n                    errors.Add($\"Could not load \\\"{filename}\\\" - unknown extension\");\r\n                }\r\n            }\r\n\r\n            if (errors.Count > 0)\r\n            {\r\n                MessageBox.Show(this, \"Error(s) loading files:\\n\" + string.Join(\"\\n\", errors));\r\n            }\r\n        }\r\n\r\n        private void AddChannelButton_Click(object sender, EventArgs e)\r\n        {\r\n            AddChannel(\"\");\r\n        }\r\n\r\n        private void CloneChannelButton_Click(object sender, EventArgs e)\r\n        {\r\n            lock (_settings)\r\n            {\r\n                var source = PropertyGrid.SelectedObject as Channel;\r\n                var index = _settings.Channels.IndexOf(source);\r\n                if (index == -1 || source == null)\r\n                {\r\n                    return;\r\n                }\r\n\r\n                // Duplicate the channel\r\n                var channel = new Channel(true);\r\n                channel.FromJson(source.ToJson(), false);\r\n                // Insert it after the selected one\r\n                _settings.Channels.Insert(index + 1, channel);\r\n                // We attach to the event last, so we must also trigger it to load the data.\r\n                channel.Changed += ChannelOnChanged;\r\n                channel.LoadDataAsync();\r\n            }\r\n\r\n            Render();\r\n        }\r\n\r\n\r\n        private void AddChannel(string filename)\r\n        {\r\n            // We create a new Channel\r\n            var channel = new Channel(true);\r\n            channel.FromJson(_programSettings.DefaultChannelSettings.ToJson(), true);\r\n            channel.Changed += ChannelOnChanged;\r\n            lock (_settings)\r\n            {\r\n                _settings.Channels.Add(channel);\r\n            }\r\n\r\n            // Setting the filename triggers a load\r\n            channel.Filename = filename;\r\n            // We trigger a render to show the \"loading\" state\r\n            Render();\r\n        }\r\n\r\n        private void ChannelOnChanged(Channel channel, bool filenameChanged)\r\n        {\r\n            // If the filename changed then we do a load\r\n            if (filenameChanged)\r\n            {\r\n                channel.LoadDataAsync();\r\n            }\r\n\r\n            BeginInvoke(new Action(() =>\r\n            {\r\n                // We check if the trackbar range needs to be changed\r\n                lock (_settings)\r\n                {\r\n                    var frameRate = _settings.FrameRate;\r\n                    var maxLength = _settings.Channels.Max(ch => ch.Length);\r\n                    var frames = maxLength.TotalSeconds * frameRate;\r\n                    PreviewTrackbar.Maximum = (int) frames;\r\n                    PreviewTrackbar.LargeChange = frameRate;\r\n                    PreviewTrackbar.TickFrequency = frameRate;\r\n                }\r\n\r\n                Render();\r\n            }));\r\n        }\r\n\r\n        private void LoadMultiDumper(string filename)\r\n        {\r\n            LocateProgram(\"multidumper.exe\", _programSettings.MultiDumperPath,\r\n                p => _programSettings.MultiDumperPath = p);\r\n            try\r\n            {\r\n                // Normalize path\r\n                filename = Path.GetFullPath(filename);\r\n\r\n                using var form = new MultiDumperForm(filename, _programSettings.MultiDumperPath, _programSettings.MultiDumperSamplingRate, _programSettings.MultiDumperLoopCount, _programSettings.MultiDumperFadeMs, _programSettings.MultiDumperGapMs, _programSettings.MultiDumperExtraSettings);\r\n                if (form.ShowDialog(this) != DialogResult.OK || form.Filenames == null)\r\n                {\r\n                    return;\r\n                }\r\n\r\n                foreach (var wavFile in form.Filenames)\r\n                {\r\n                    AddChannel(wavFile);\r\n                }\r\n            }\r\n            catch (Exception ex)\r\n            {\r\n                MessageBox.Show($\"Error running MultiDumper: {ex}\");\r\n            }\r\n        }\r\n\r\n        private void LoadSid(string filename)\r\n        {\r\n            LocateProgram(\"sidplayfp.exe\", _programSettings.SidPlayPath,\r\n                p => _programSettings.SidPlayPath = p);\r\n            try\r\n            {\r\n                using var form = new SidPlayForm(filename, _programSettings.SidPlayPath);\r\n                if (form.ShowDialog(this) != DialogResult.OK || form.Filenames == null)\r\n                {\r\n                    return;\r\n                }\r\n\r\n                foreach (var wavFile in form.Filenames)\r\n                {\r\n                    AddChannel(wavFile);\r\n                }\r\n            }\r\n            catch (Exception ex)\r\n            {\r\n                MessageBox.Show($\"Error running SidPlay: {ex}\");\r\n            }\r\n        }\r\n\r\n        private void LocateProgram(string filename, string currentValue, Action<string> saveToSettings)\r\n        {\r\n            // Get path if we don't already have it\r\n            if (string.IsNullOrEmpty(currentValue) || !File.Exists(currentValue))\r\n            {\r\n                // Check if it's in the program directory\r\n                var directory = Path.GetDirectoryName(Assembly.GetCallingAssembly().Location);\r\n                if (directory != null)\r\n                {\r\n                    var path = Path.Combine(directory, filename);\r\n                    if (File.Exists(path))\r\n                    {\r\n                        saveToSettings(path);\r\n                        SaveProgramSettings();\r\n                        return;\r\n                    }\r\n                }\r\n\r\n                // Else browse for it\r\n                using var ofd = new OpenFileDialog();\r\n                ofd.Title = $\"Please locate {filename}\";\r\n                ofd.Filter = $\"{filename}|{filename}|All files (*.*)|*.*\";\r\n                if (ofd.ShowDialog(this) == DialogResult.OK)\r\n                {\r\n                    saveToSettings(ofd.FileName);\r\n                    SaveProgramSettings();\r\n                }\r\n            }\r\n        }\r\n\r\n        private void AutoScale_Click(object sender, EventArgs e)\r\n        {\r\n            lock (_settings)\r\n            {\r\n                if (_settings.Channels.Count <= 0)\r\n                {\r\n                    return;\r\n                }\r\n\r\n                // Compute the scale for the channel with the highest value\r\n                var scale = _settings.AutoScaleHeight / 100 / _settings.Channels.Max(channel => channel.Max);\r\n                foreach (var channel in _settings.Channels)\r\n                {\r\n                    channel.Scale = scale;\r\n                }\r\n            }\r\n        }\r\n\r\n        private void Render()\r\n        {\r\n            // If this is called while a render is in process, then wait until that is done, and do one at the end.\r\n            lock (_renderLock)\r\n            {\r\n                // We update this here so the worker thread will get the latest value.\r\n                _renderPosition = (float) PreviewTrackbar.Value / PreviewTrackbar.Maximum;\r\n\r\n                // We have two flags to signal the need to render.\r\n                // One indicates that we need to render; this can be set while rendering\r\n                // to cause it to render again when done.\r\n                if (_renderNeeded)\r\n                {\r\n                    return;\r\n                }\r\n\r\n                _renderNeeded = true;\r\n\r\n                // The second flag indicates that we have started the task to render.\r\n                // This ensures we don't start two render tasks at the same time.\r\n                if (_renderActive)\r\n                {\r\n                    return;\r\n                }\r\n\r\n                _renderActive = true;\r\n            }\r\n\r\n            // And finally we start the task.\r\n            Task.Factory.StartNew(() =>\r\n            {\r\n                // We repeatedly render while the flag says we need to render.\r\n                for (;;)\r\n                {\r\n                    float renderPosition;\r\n                    lock (_renderLock)\r\n                    {\r\n                        _renderNeeded = false;\r\n                        renderPosition = _renderPosition;\r\n                    }\r\n\r\n                    // Create a renderer\r\n                    var renderer = CreateWaveformRenderer();\r\n\r\n                    // Render a bitmap\r\n                    var bitmap = renderer.RenderFrame(renderPosition);\r\n\r\n                    BeginInvoke(new Action(() =>\r\n                    {\r\n                        // Swap it with whatever is in the preview control\r\n                        var oldImage = Preview.Image;\r\n                        // ReSharper disable once AccessToModifiedClosure\r\n                        Preview.Image = bitmap;\r\n                        Preview.Refresh();\r\n                        oldImage?.Dispose();\r\n                    }));\r\n\r\n                    // If the flag got set while we were rendering, do it again\r\n                    lock (_renderLock)\r\n                    {\r\n                        if (_renderNeeded)\r\n                        {\r\n                            continue;\r\n                        }\r\n\r\n                        _renderActive = false;\r\n                        break;\r\n                    }\r\n                }\r\n            });\r\n        }\r\n\r\n        private WaveformRenderer CreateWaveformRenderer()\r\n        {\r\n            // This can be on a worker thread, so we need to lock...\r\n            lock (_settings)\r\n            {\r\n                var renderer = new WaveformRenderer\r\n                {\r\n                    BackgroundColor = _settings.BackgroundColor,\r\n                    BackgroundImage = File.Exists(_settings.BackgroundImageFilename)\r\n                        ? Image.FromFile(_settings.BackgroundImageFilename)\r\n                        : null,\r\n                    Width = _settings.Width,\r\n                    Height = _settings.Height,\r\n                    Columns = _settings.Columns,\r\n                    FramesPerSecond = _settings.FrameRate,\r\n                    RenderingBounds = _settings.GetBounds()\r\n                };\r\n\r\n                if (_settings.Channels.Count > 0)\r\n                {\r\n                    // We don't support multiple sampling rates, but this lets us ignore \"empty\" tracks.\r\n                    renderer.SamplingRate = _settings.Channels.Max(c => c.SampleRate);\r\n                }\r\n\r\n                foreach (var channel in _settings.Channels)\r\n                {\r\n                    renderer.AddChannel(channel);\r\n                }\r\n\r\n                return renderer;\r\n            }\r\n        }\r\n\r\n        private void LeftButton_Click(object sender, EventArgs e)\r\n        {\r\n            MoveChannel(PropertyGrid.SelectedObject as Channel, -1);\r\n        }\r\n\r\n        private void RightButton_Click(object sender, EventArgs e)\r\n        {\r\n            MoveChannel(PropertyGrid.SelectedObject as Channel, +1);\r\n        }\r\n\r\n        private void MoveChannel(Channel channel, int delta)\r\n        {\r\n            lock (_settings)\r\n            {\r\n                var index = _settings.Channels.IndexOf(channel);\r\n                if (index == -1)\r\n                {\r\n                    return;\r\n                }\r\n\r\n                var newIndex = index + delta;\r\n                if (newIndex < 0 || newIndex >= _settings.Channels.Count)\r\n                {\r\n                    return;\r\n                }\r\n\r\n                _settings.Channels.RemoveAt(index);\r\n                _settings.Channels.Insert(newIndex, channel);\r\n            }\r\n\r\n            Render();\r\n        }\r\n\r\n        private void RemoveButton_Click(object sender, EventArgs e)\r\n        {\r\n            if (PropertyGrid.SelectedObject is not Channel channel)\r\n            {\r\n                return;\r\n            }\r\n\r\n            channel.Changed -= ChannelOnChanged;\r\n            lock (_settings)\r\n            {\r\n                _settings.Channels.Remove(channel);\r\n            }\r\n\r\n            channel.Dispose();\r\n            PropertyGrid.SelectedObject = null;\r\n            Render();\r\n        }\r\n\r\n        private void Preview_MouseClick(object sender, MouseEventArgs e)\r\n        {\r\n            PropertyGrid.SelectedObject = GetClickedChannel(e.X, e.Y);\r\n            if (PropertyGrid.SelectedObject != null)\r\n            {\r\n                tabControl.SelectedTab = channelsTab;\r\n            }\r\n        }\r\n\r\n        private Channel GetClickedChannel(int clickX, int clickY)\r\n        {\r\n            // Determine which channel was clicked\r\n            // This is tricky because the preview is scaling the image to fit, but we don't know the details\r\n            // So we need to map the click into the image space\r\n            // First we map the click to the preview space in the range 0..1 in each dimension\r\n            var x = (double) clickX / Preview.Width;\r\n            var y = (double) clickY / Preview.Height;\r\n            // Next we map that to image space\r\n            var imageAspectRatio = (double) Preview.Image.Width / Preview.Image.Height;\r\n            var previewAspectRatio = (double) Preview.Width / Preview.Height;\r\n            if (previewAspectRatio > imageAspectRatio)\r\n            {\r\n                // Preview is wider, we have pillar-boxing\r\n                // So the y one is correct, x needs to be modified:\r\n                // +--+-----+--+\r\n                // |  |     |  |\r\n                // +--+-----+--+\r\n                x = (x - 0.5) * previewAspectRatio / imageAspectRatio + 0.5;\r\n                if (x is < 0 or > 1)\r\n                {\r\n                    return null;\r\n                }\r\n            }\r\n            else\r\n            {\r\n                // Image is wider, we have letterboxing\r\n                y = (y - 0.5) * imageAspectRatio / previewAspectRatio + 0.5;\r\n                if (y is < 0 or > 1)\r\n                {\r\n                    return null;\r\n                }\r\n            }\r\n\r\n            lock (_settings)\r\n            {\r\n                // Then we map that into the range for the borders in the image\r\n                // 0,0------------------+\r\n                // |                    |\r\n                // |    0,0--------+    |\r\n                // |    |          |    |\r\n                // |    +--------1,1    |\r\n                // |                    |\r\n                // +--------------------1,1\r\n                x = (x - (double)_settings.MarginLeft / _settings.Width) * _settings.Width /\r\n                    (_settings.Width - _settings.MarginLeft - _settings.MarginRight);\r\n                if (x is < 0.0 or > 1.0)\r\n                {\r\n                    return null;\r\n                }\r\n\r\n                y = (y - (double)_settings.MarginTop / _settings.Height) * _settings.Height /\r\n                    (_settings.Height - _settings.MarginTop - _settings.MarginBottom);\r\n                if (y is < 0.0 or > 1.0)\r\n                {\r\n                    return null;\r\n                }\r\n\r\n                // Then we map that to the row/column space\r\n                var column = (int) (_settings.Columns * x);\r\n                var numRows = _settings.Channels.Count / _settings.Columns +\r\n                              (_settings.Channels.Count % _settings.Columns == 0 ? 0 : 1);\r\n                var row = (int) (numRows * y);\r\n                var index = row * _settings.Columns + column;\r\n                if (index >= _settings.Channels.Count || index < 0)\r\n                {\r\n                    return null;\r\n                }\r\n                return _settings.Channels[index];\r\n            }\r\n        }\r\n\r\n        private void CopySettingsButton_Click(object sender, EventArgs e)\r\n        {\r\n            if (PropertyGrid.SelectedObject is not Channel source)\r\n            {\r\n                return;\r\n            }\r\n\r\n            lock (_settings)\r\n            {\r\n                var json = source.ToJson();\r\n                foreach (var channel in _settings.Channels.Where(channel => channel != source))\r\n                {\r\n                    channel.FromJson(json, true);\r\n                }\r\n            }\r\n        }\r\n\r\n        private void ControlValueChanged(object sender, EventArgs e)\r\n        {\r\n            try\r\n            {\r\n                lock (_settings)\r\n                {\r\n                    _settings.FromControls(this);\r\n                }\r\n            }\r\n            catch (Exception exception)\r\n            {\r\n                MessageBox.Show(exception.Message);\r\n            }\r\n\r\n            Render();\r\n        }\r\n\r\n        private void BackgroundImageControl_Click(object sender, EventArgs e)\r\n        {\r\n            using var ofd = new OpenFileDialog();\r\n            ofd.Title = \"Select an image\";\r\n            ofd.Filter = \"Image files (*.png;*.gif;*.jpg;*.jpeg;*.bmp;*.wmf)|*.png;*.gif;*.jpg;*.jpeg;*.bmp;*.wmf|All files (*.*)|*.*\";\r\n            if (ofd.ShowDialog(this) != DialogResult.OK)\r\n            {\r\n                BackgroundImageControl.Image = null;\r\n                lock (_settings)\r\n                {\r\n                    _settings.BackgroundImageFilename = null;\r\n                }\r\n            }\r\n            else\r\n            {\r\n                BackgroundImageControl.ImageLocation = ofd.FileName;\r\n                lock (_settings)\r\n                {\r\n                    _settings.BackgroundImageFilename = ofd.FileName;\r\n                }\r\n            }\r\n\r\n            Render();\r\n        }\r\n\r\n        private void RenderButton_Click(object sender, EventArgs e)\r\n        {\r\n            if (_progress != null)\r\n            {\r\n                // Cancel the rendering\r\n                _progress.Cancel();\r\n                return;\r\n            }\r\n\r\n            _progress = new MainFormProgressOutput(this);\r\n            var outputs = new List<IGraphicsOutput> {_progress};\r\n\r\n            lock (_settings)\r\n            {\r\n                if (_settings.Preview.Enabled)\r\n                {\r\n                    outputs.Add(new PreviewOutput(_settings.Preview.Frameskip));\r\n                }\r\n\r\n                if (_settings.EncodeVideo.Enabled)\r\n                {\r\n                    LocateProgram(\"ffmpeg.exe\", _programSettings.FfmpegPath,\r\n                        p => _programSettings.FfmpegPath = p);\r\n\r\n                    using var saveFileDialog = new SaveFileDialog();\r\n                    saveFileDialog.Title = \"Select destination\";\r\n                    saveFileDialog.Filter = \"Video files (*.mp4;*.mkv;*.avi;*.qt)|*.mp4;*.mkv;*.avi;*.qt|All files (*.*)|*.*\";\r\n                    if (saveFileDialog.ShowDialog(this) != DialogResult.OK)\r\n                    {\r\n                        // Cancel the whole operation\r\n                        _progress = null;\r\n                        foreach (var output in outputs)\r\n                        {\r\n                            output.Dispose();\r\n                        }\r\n                        return;\r\n                    }\r\n\r\n                    var outputFilename = saveFileDialog.FileName;\r\n\r\n                    if (_settings.MasterAudio.IsAutomatic)\r\n                    {\r\n                        try\r\n                        {\r\n                            var filename = outputFilename + \".wav\";\r\n                            Mixer.MixToFile(_settings.Channels, filename, MasterMixReplayGain.Checked);\r\n                            MasterAudioPath.Text = filename;\r\n                            _settings.MasterAudio.Path = filename;\r\n                        }\r\n                        catch (Exception ex)\r\n                        {\r\n                            MessageBox.Show(this, $\"Failed to mix audio: {ex.Message}\");\r\n                        }\r\n                    }\r\n                    else\r\n                    {\r\n                        _settings.MasterAudio.Path = MasterAudioPath.Text;\r\n                    }\r\n\r\n                    outputs.Add(new FfmpegOutput(\r\n                        _programSettings.FfmpegPath,\r\n                        outputFilename,\r\n                        _settings.Width,\r\n                        _settings.Height,\r\n                        _settings.FrameRate,\r\n                        string.Join(\" \", _programSettings.FfmpegExtraParameters, _settings.EncodeVideo.ExtraParameters),\r\n                        _settings.MasterAudio.Path,\r\n                        _settings.EncodeVideo.VideoCodec,\r\n                        _settings.EncodeVideo.AudioCodec,\r\n                        true));\r\n                }\r\n            }\r\n\r\n            RenderButton.Text = \"Cancel render\";\r\n\r\n            // Start a background thread to do the rendering work\r\n            Task.Factory.StartNew(() =>\r\n            {\r\n                try\r\n                {\r\n                    var renderer = CreateWaveformRenderer();\r\n                    renderer.Render(outputs, _settings.RenderThreads, false);\r\n                }\r\n                catch (Exception exception)\r\n                {\r\n                    BeginInvoke(new Action(() => MessageBox.Show(this, exception.Message)));\r\n                }\r\n                finally\r\n                {\r\n                    foreach (var graphicsOutput in outputs)\r\n                    {\r\n                        graphicsOutput.Dispose();\r\n                    }\r\n\r\n                    _progress = null;\r\n                    BeginInvoke(new Action(() => RenderButton.Text = \"Render\"));\r\n                }\r\n            });\r\n        }\r\n\r\n        private class MainFormProgressOutput : IGraphicsOutput\r\n        {\r\n            private readonly SidWizPlusGui _form;\r\n            private readonly Stopwatch _stopwatch;\r\n            private int _frameIndex;\r\n            private DateTime _updateTime = DateTime.MinValue;\r\n            private readonly TimeSpan _updateInterval = TimeSpan.FromMilliseconds(100);\r\n            private bool _cancelRequested;\r\n\r\n            public MainFormProgressOutput(SidWizPlusGui form)\r\n            {\r\n                _form = form;\r\n                _stopwatch = Stopwatch.StartNew();\r\n            }\r\n\r\n            public void Cancel()\r\n            {\r\n                _cancelRequested = true;\r\n            }\r\n\r\n            public void Dispose()\r\n            {\r\n                _form.BeginInvoke(new Action(() =>\r\n                {\r\n                    if (_form.IsDisposed || !_form.Visible)\r\n                    {\r\n                        return;\r\n                    }\r\n\r\n                    _form.Text = \"SidWizPlusGUI\";\r\n                }));\r\n            }\r\n\r\n            public void Write(SKImage image, byte[] data, double fractionComplete, TimeSpan length)\r\n            {\r\n                if (_cancelRequested)\r\n                {\r\n                    throw new Exception(\"Render cancelled\");\r\n                }\r\n\r\n                ++_frameIndex;\r\n\r\n                // We don't need the data, just the progress\r\n                var now = DateTime.UtcNow;\r\n                if (now - _updateTime < _updateInterval)\r\n                {\r\n                    return;\r\n                }\r\n\r\n                _updateTime = now;\r\n                var elapsedSeconds = _stopwatch.Elapsed.TotalSeconds;\r\n                var fps = _frameIndex / elapsedSeconds;\r\n                var eta = TimeSpan.FromSeconds(elapsedSeconds / fractionComplete - elapsedSeconds);\r\n                _form.BeginInvoke(new Action(() =>\r\n                {\r\n                    if (_form.IsDisposed || !_form.Visible)\r\n                    {\r\n                        return;\r\n                    }\r\n\r\n                    _form.Text = $\"SidWizPlusGUI - {fractionComplete:P} of {length} @ {fps:F}fps, ETA {eta:g}\";\r\n                }));\r\n            }\r\n        }\r\n\r\n        private void AutogenerateMasterMix_CheckedChanged(object sender, EventArgs e)\r\n        {\r\n            MasterMixReplayGain.Enabled = AutogenerateMasterMix.Checked;\r\n        }\r\n\r\n        private void SetMasterAudioPath(object sender, EventArgs e)\r\n        {\r\n            using var ofd = new OpenFileDialog();\r\n            ofd.Title = \"Select master audio file\";\r\n            ofd.Filter = \"Wave audio files (*.wav)|*.wav|All files (*.*)|*.*\";\r\n            if (ofd.ShowDialog(this) != DialogResult.OK)\r\n            {\r\n                return;\r\n            }\r\n\r\n            MasterAudioPath.Text = ofd.FileName;\r\n        }\r\n\r\n        private void LoadProgramSettings(string settingsPath)\r\n        {\r\n            try\r\n            {\r\n                _programSettings = JsonConvert.DeserializeObject<ProgramSettings>(File.ReadAllText(settingsPath));\r\n            }\r\n            catch (Exception)\r\n            {\r\n                // Ignore it\r\n            }\r\n        }\r\n\r\n        private void SaveProgramSettings()\r\n        {\r\n            var path = GetSettingsPath();\r\n            var directory = Path.GetDirectoryName(path);\r\n            if (directory == null)\r\n            {\r\n                return;\r\n            }\r\n\r\n            Directory.CreateDirectory(directory);\r\n            File.WriteAllText(path, JsonConvert.SerializeObject(_programSettings));\r\n\r\n            // It may be filled in automatically, so we need to refresh the UI\r\n            ProgramSettingsGrid.SelectedObject = _programSettings;\r\n        }\r\n\r\n        private static string GetSettingsPath()\r\n        {\r\n            return Path.Combine(\r\n                Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),\r\n                \"SidWizPlus\",\r\n                \"settings.json\");\r\n        }\r\n\r\n        private void Initialize(object sender, EventArgs e)\r\n        {\r\n            HighDpiHelper.AdjustControlImagesDpiScale(this);\r\n            var settingsPath = GetSettingsPath();\r\n            LoadProgramSettings(settingsPath);\r\n            if (File.Exists(settingsPath))\r\n            {\r\n                _settingsWatcher = new FileSystemWatcher\r\n                {\r\n                    Path = Path.GetDirectoryName(settingsPath),\r\n                    Filter = Path.GetFileName(settingsPath)\r\n                };\r\n                _settingsWatcher.Changed += (_, _) => LoadProgramSettings(settingsPath);\r\n            }\r\n\r\n            ProgramSettingsGrid.BeginInvoke(new Action(() => { ProgramSettingsGrid.SelectedObject = _programSettings; }));\r\n\r\n            lock (_settings)\r\n            {\r\n                _settings.ToControls(this);\r\n            }\r\n\r\n            // Use exe icon as form icon\r\n            Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);\r\n\r\n            // Replicate the right-click menu into the toolbar automatically, so I don't have to maintain both\r\n            int indexToInsertAt = toolStrip1.Items.IndexOf(ChannelToolstripItemsSeparator) + 1;\r\n            foreach (var item in contextMenuStrip1.Items.Cast<ToolStripItem>().Reverse())\r\n            {\r\n                ToolStripItem newItem = null;\r\n                switch (item)\r\n                {\r\n                    case ToolStripMenuItem when item == removeChannelToolStripMenuItem:\r\n                        // This is handled separately\r\n                        continue;\r\n                    case ToolStripMenuItem:\r\n                        newItem = new ToolStripButton\r\n                        {\r\n                            Image = item.Image,\r\n                            Text = item.Text,\r\n                            DisplayStyle = ToolStripItemDisplayStyle.Image\r\n                        };\r\n                        newItem.Click += (_, _) => item.PerformClick();\r\n                        break;\r\n                    case ToolStripSeparator:\r\n                        newItem = new ToolStripSeparator();\r\n                        break;\r\n                }\r\n\r\n                if (newItem != null)\r\n                {\r\n                    toolStrip1.Items.Insert(indexToInsertAt, newItem);\r\n                }\r\n            }\r\n\r\n            // Load settings if passed on the commandline\r\n            foreach (var arg in Environment.GetCommandLineArgs()\r\n                .Skip(1)\r\n                .Where(x => x.ToLowerInvariant().EndsWith(\".sidwizplus.json\")))\r\n            {\r\n                LoadSettingsFromFile(arg);\r\n            }\r\n        }\r\n\r\n        private void RemoveSilentChannels(object sender, EventArgs e)\r\n        {\r\n            lock (_settings)\r\n            {\r\n                foreach (var channel in _settings.Channels.Where(c => c.IsSilent).ToList())\r\n                {\r\n                    _settings.Channels.Remove(channel);\r\n                    if (PropertyGrid.SelectedObject == channel)\r\n                    {\r\n                        PropertyGrid.SelectedObject = null;\r\n                    }\r\n\r\n                    channel.Dispose();\r\n                }\r\n            }\r\n\r\n            Render();\r\n        }\r\n\r\n        private void RemoveAllChannels(object sender, EventArgs e)\r\n        {\r\n            lock (_settings)\r\n            {\r\n                foreach (var channel in _settings.Channels)\r\n                {\r\n                    channel.Dispose();\r\n                }\r\n\r\n                _settings.Channels.Clear();\r\n            }\r\n\r\n            PropertyGrid.SelectedObject = null;\r\n            Render();\r\n        }\r\n\r\n        private void SaveProject(object sender, EventArgs e)\r\n        {\r\n            using var sfd = new SaveFileDialog();\r\n            sfd.Filter = \"SidWizPlus settings (*.sidwizplus.json)|*.sidwizplus.json|All files (*.*)|*.*\";\r\n            if (sfd.ShowDialog(this) != DialogResult.OK)\r\n            {\r\n                return;\r\n            }\r\n\r\n            lock (_settings)\r\n            {\r\n                File.WriteAllText(sfd.FileName, JsonConvert.SerializeObject(_settings, new JsonSerializerSettings\r\n                {\r\n                    Formatting = Formatting.Indented\r\n                }));\r\n            }\r\n        }\r\n\r\n        private void LoadProject(object sender, EventArgs e)\r\n        {\r\n            using var ofd = new OpenFileDialog();\r\n            ofd.Filter = \"SidWizPlus settings (*.sidwizplus.json)|*.sidwizplus.json|All files (*.*)|*.*\";\r\n            if (ofd.ShowDialog(this) != DialogResult.OK)\r\n            {\r\n                return;\r\n            }\r\n\r\n            LoadSettingsFromFile(ofd.FileName);\r\n        }\r\n\r\n        private void LoadSettingsFromFile(string path)\r\n        {\r\n            lock (_settings)\r\n            {\r\n                var newSettings = JsonConvert.DeserializeObject<Settings>(File.ReadAllText(path));\r\n\r\n                // We inspect to decide what to do...\r\n                var clearExistingChannels = false;\r\n                var discardNewChannels = false;\r\n                var removeMissingFiles = false;\r\n                var removeDuplicates = false;\r\n                if (newSettings.Channels.Any())\r\n                {\r\n                    // No new channels means nothing to check...\r\n\r\n                    // If we already have channels, ask if the user wants to add or replace.\r\n                    if (_settings.Channels.Any())\r\n                    {\r\n                        using var dialog = new TaskDialog();\r\n                        dialog.InstructionText = \"Loading new channels\";\r\n                        dialog.Text =\r\n                            $\"The settings being loaded contain {newSettings.Channels.Count} channels. What do you want to do?\";\r\n                        dialog.Cancelable = true;\r\n                        dialog.Icon = TaskDialogStandardIcon.Warning;\r\n                        dialog.StandardButtons = TaskDialogStandardButtons.Cancel;\r\n                        dialog.DetailsExpandedText = $\"\"\"\r\n                                                      Existing channels:\r\n                                                      {string.Join(\"\\n\", PrintFilenames(_settings.Channels, null))}\r\n                                                      New channels:\r\n                                                      {string.Join(\"\\n\", PrintFilenames(newSettings.Channels, _settings.Channels))}\r\n                                                      \"\"\";\r\n                        AddDialogButton(dialog, \"Add\", \"Add to the existing channels\", () => {});\r\n                        AddDialogButton(dialog, \"Replace\", \"Replace the existing channels\",\r\n                            () => { clearExistingChannels = true; });\r\n                        AddDialogButton(dialog, \"Discard\", \"Discard the new channels\",\r\n                            () => { discardNewChannels = true; });\r\n\r\n                        if (dialog.Show() == TaskDialogResult.Cancel)\r\n                        {\r\n                            return;\r\n                        }\r\n                    }\r\n                }\r\n\r\n                if (!discardNewChannels && !clearExistingChannels)\r\n                {\r\n                    // If the new channels refer to files that don't exist, the user may not want them.\r\n                    var missingFiles = newSettings.Channels\r\n                        .Select(x => x.Filename).Where(x => !File.Exists(x))\r\n                        .Distinct()\r\n                        .ToList();\r\n\r\n                    if (missingFiles.Any())\r\n                    {\r\n                        using var dialog = new TaskDialog();\r\n                        dialog.InstructionText = \"Missing audio files\";\r\n                        dialog.Text =\r\n                            $\"The settings being loaded contain {newSettings.Channels.Count} channels, but {missingFiles.Count} refer to files that don't exist. Do you want to add them anyway?\";\r\n                        dialog.Cancelable = true;\r\n                        dialog.Icon = TaskDialogStandardIcon.Warning;\r\n                        dialog.StandardButtons = TaskDialogStandardButtons.Yes | TaskDialogStandardButtons.No |\r\n                                                 TaskDialogStandardButtons.Cancel;\r\n                        dialog.DetailsExpandedText = string.Join(\"\\n\", PrintFilenames(newSettings.Channels, null));\r\n\r\n                        switch (dialog.Show())\r\n                        {\r\n                            case TaskDialogResult.Cancel:\r\n                                return;\r\n                            case TaskDialogResult.Yes:\r\n                                removeMissingFiles = true;\r\n                                // Also clear the channels from the settings for the next check\r\n                                newSettings.Channels.RemoveAll(x => missingFiles.Contains(x.Filename));\r\n                                break;\r\n                        }\r\n                    }\r\n\r\n                    // Now also check for duplicates\r\n                    if (_settings.Channels.Any())\r\n                    {\r\n                        var duplicates = newSettings.Channels\r\n                            .Where(x => _settings.Channels.Any(y =>\r\n                                Path.GetFullPath(y.Filename) == Path.GetFullPath(x.Filename)))\r\n                            .ToList();\r\n                        if (duplicates.Any())\r\n                        {\r\n                            using var dialog = new TaskDialog();\r\n                            dialog.InstructionText = \"Duplicate audio files\";\r\n                            dialog.Text =\r\n                                $\"The settings being loaded contain {duplicates.Count} channels where the file is already loaded. Do you want to add them anyway?\";\r\n                            dialog.Cancelable = true;\r\n                            dialog.Icon = TaskDialogStandardIcon.Warning;\r\n                            dialog.StandardButtons = TaskDialogStandardButtons.Yes | TaskDialogStandardButtons.No |\r\n                                                     TaskDialogStandardButtons.Cancel;\r\n                            dialog.DetailsExpandedText = \"New channels:\\n\" + string.Join(\"\\n\",\r\n                                PrintFilenames(newSettings.Channels, _settings.Channels));\r\n\r\n                            switch (dialog.Show())\r\n                            {\r\n                                case TaskDialogResult.Cancel:\r\n                                    return;\r\n                                case TaskDialogResult.No:\r\n                                    removeDuplicates = true;\r\n                                    break;\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n\r\n                // Deserialize again on top, so partial JSON loads will work\r\n                if (clearExistingChannels)\r\n                {\r\n                    _settings.Channels.Clear();\r\n                }\r\n                var countOfOldChannels = _settings.Channels.Count;\r\n                JsonConvert.PopulateObject(File.ReadAllText(path), _settings);\r\n                if (discardNewChannels)\r\n                {\r\n                    var oldChannels = _settings.Channels.Take(countOfOldChannels).ToList();\r\n                    _settings.Channels.Clear();\r\n                    _settings.Channels.AddRange(oldChannels);\r\n                }\r\n\r\n                // Then remove channels as specified\r\n                if (removeMissingFiles)\r\n                {\r\n                    var channelsToRemove = _settings.Channels\r\n                        .Skip(countOfOldChannels)\r\n                        .Where(x => !File.Exists(x.Filename))\r\n                        .ToList();\r\n                    foreach (var channel in channelsToRemove)\r\n                    {\r\n                        _settings.Channels.Remove(channel);\r\n                    }\r\n                }\r\n\r\n                if (removeDuplicates)\r\n                {\r\n                    var oldChannels = _settings.Channels\r\n                        .Take(countOfOldChannels)\r\n                        .Select(x => Path.GetFullPath(x.Filename))\r\n                        .ToList();\r\n                    var channelsToRemove = _settings.Channels\r\n                        .Skip(countOfOldChannels)\r\n                        .Where(x => oldChannels.Contains(Path.GetFullPath(x.Filename)))\r\n                        .ToList();\r\n                    foreach (var channel in channelsToRemove)\r\n                    {\r\n                        _settings.Channels.Remove(channel);\r\n                    }\r\n                }\r\n\r\n                // Finally, trigger a load on the new channels (and attach our event handler)\r\n                foreach (var channel in _settings.Channels.Skip(countOfOldChannels))\r\n                {\r\n                    channel.Changed += ChannelOnChanged;\r\n                    channel.LoadDataAsync();\r\n                }\r\n\r\n                _settings.ToControls(this);\r\n            }\r\n        }\r\n\r\n        private static IEnumerable<string> PrintFilenames(List<Channel> channels, List<Channel> existingChannels)\r\n        {\r\n            foreach (var channel in channels)\r\n            {\r\n                string icon;\r\n                if (!File.Exists(channel.Filename))\r\n                {\r\n                    icon = \"❌\";\r\n                }\r\n                else if (\r\n                    existingChannels != null && \r\n                    !existingChannels.Contains(channel) && \r\n                    existingChannels.Any(x => x.Filename == channel.Filename))\r\n                {\r\n                    icon = \"💥\";\r\n                }\r\n                else\r\n                {\r\n                    icon = \"🔉\";\r\n                }\r\n\r\n                yield return $@\"{icon} {channel.Filename}\";\r\n            }\r\n            yield return \"❌ = file not found\";\r\n            yield return \"💥 = file is already present\";\r\n        }\r\n\r\n        private static void AddDialogButton(TaskDialog dialog, string text, string instruction, Action action)\r\n        {\r\n            var button = new TaskDialogCommandLink\r\n            {\r\n                Text = text,\r\n                Instruction = instruction,\r\n            };\r\n            button.Click += (_, _) =>\r\n            {\r\n                action();\r\n                // ReSharper disable once AccessToDisposedClosure\r\n                dialog.Close(TaskDialogResult.Ok);\r\n            };\r\n            dialog.Controls.Add(button);\r\n        }\r\n\r\n        private void CopyChannelSettings(object sender, EventArgs e)\r\n        {\r\n            if (PropertyGrid.SelectedObject is not Channel source)\r\n            {\r\n                return;\r\n            }\r\n\r\n            Clipboard.SetText(source.ToJson());\r\n        }\r\n\r\n        private void PasteChannelSettings(object sender, EventArgs e)\r\n        {\r\n            if (PropertyGrid.SelectedObject is not Channel channel)\r\n            {\r\n                return;\r\n            }\r\n\r\n            try\r\n            {\r\n                channel.FromJson(Clipboard.GetText(), true);\r\n            }\r\n            catch (Exception exception)\r\n            {\r\n                MessageBox.Show(this, $\"Error while pasting: \\n\\n{exception}\");\r\n            }\r\n        }\r\n\r\n        private void SplitChannel(object sender, EventArgs e)\r\n        {\r\n            if (PropertyGrid.SelectedObject is not Channel channel)\r\n            {\r\n                return;\r\n            }\r\n\r\n            if (channel.IsMono())\r\n            {\r\n                if (MessageBox.Show(\r\n                    this,\r\n                    \"Data is not stereo, do you want to clone the channel instead?\",\r\n                    \"Split channel\",\r\n                    MessageBoxButtons.YesNo,\r\n                    MessageBoxIcon.Question,\r\n                    MessageBoxDefaultButton.Button2) == DialogResult.Yes)\r\n                {\r\n                    CloneChannelButton_Click(sender, e);\r\n                }\r\n\r\n                return;\r\n            }\r\n\r\n            // We clone the channel for the right side\r\n            var right = new Channel(true);\r\n            var json = channel.ToJson();\r\n            right.FromJson(json, false);\r\n            right.Side = Channel.Sides.Right;\r\n            right.Changed += ChannelOnChanged;\r\n            right.LoadDataAsync();\r\n\r\n            // We switch the existing channel to the left side\r\n            channel.Side = Channel.Sides.Left;\r\n            channel.LoadDataAsync();\r\n\r\n            lock (_settings)\r\n            {\r\n                var index = _settings.Channels.IndexOf(channel);\r\n                _settings.Channels.Insert(index + 1, right);\r\n            }\r\n\r\n            // We trigger a render to show the \"loading\" state\r\n            Render();\r\n        }\r\n\r\n        private void RemoveAllLabels(object sender, EventArgs e)\r\n        {\r\n            lock (_settings)\r\n            {\r\n                foreach (var channel in _settings.Channels)\r\n                {\r\n                    channel.Label = \"\";\r\n                }\r\n            }\r\n        }\r\n\r\n        private void HandleClosing(object sender, FormClosingEventArgs e)\r\n        {\r\n            _settingsWatcher?.Dispose();\r\n            SaveProgramSettings();\r\n        }\r\n\r\n        private void SaveAsDefaultSettings(object sender, EventArgs e)\r\n        {\r\n            if (PropertyGrid.SelectedObject is not Channel source)\r\n            {\r\n                return;\r\n            }\r\n\r\n            _programSettings.DefaultChannelSettings.FromJson(source.ToJson(), true);\r\n            SaveProgramSettings();\r\n        }\r\n\r\n        private void ResetToDefaultSettings(object sender, EventArgs e)\r\n        {\r\n            if (PropertyGrid.SelectedObject is not Channel channel)\r\n            {\r\n                return;\r\n            }\r\n\r\n            lock (_settings)\r\n            {\r\n                channel.FromJson(_programSettings.DefaultChannelSettings.ToJson(), true);\r\n            }\r\n        }\r\n\r\n        private void PropertyGrid_SelectedObjectsChanged(object sender, EventArgs e)\r\n        {\r\n            PropertyGrid.Visible = PropertyGrid.SelectedObject != null;\r\n            ChannelsHelpLabel.Visible = PropertyGrid.SelectedObject == null;\r\n        }\r\n\r\n        private void VideoCodec_DropDown(object sender, EventArgs e)\r\n        {\r\n            if (!File.Exists(_programSettings.FfmpegPath) || AudioCodec.Items.Count > 0 || VideoCodec.Items.Count > 0)\r\n            {\r\n                return;\r\n            }\r\n\r\n            try\r\n            {\r\n                // We run FFMPEG to find what codecs it supports\r\n                using var process = new ProcessWrapper(_programSettings.FfmpegPath, \"-encoders\");\r\n                process.WaitForExit();\r\n                foreach (var grouping in process\r\n                    .Lines()\r\n                    .Select(x => Regex.Match(x, \"^ (?<type>[AV])[^ ]+ (?<name>[^ ]+) +(?<description>.+)$\"))\r\n                    .Where(m => m.Success && !m.Groups[\"name\"].Value.Contains(\"=\"))\r\n                    .Select(m => new Codec(m.Groups[\"type\"].Value, m.Groups[\"description\"].Value, m.Groups[\"name\"].Value))\r\n                    .GroupBy(c => c.Type))\r\n                {\r\n                    var combo = grouping.Key switch\r\n                    {\r\n                        \"A\" => AudioCodec,\r\n                        \"V\" => VideoCodec,\r\n                        _ => null\r\n                    };\r\n                    combo?.Items.Clear();\r\n                    combo?.Items.AddRange(grouping.OrderBy(x => x.ToString()).ToArray<object>());\r\n                }\r\n            }\r\n            catch (Exception ex)\r\n            {\r\n                MessageBox.Show($\"Error querying FFMPEG: {ex.Message}\");\r\n            }\r\n        }\r\n\r\n        private record Codec(string Type, string Description, string Name)\r\n        {\r\n            public string Type { get; } = Type;\r\n            public string Description { get; } = Description;\r\n            public string Name { get; } = Name;\r\n\r\n            public override string ToString()\r\n            {\r\n                return $\"{Name} {Description}\";\r\n            }\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "SidWiz/SidWizPlusGUI.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\r\n  <PropertyGroup>\r\n    <TargetFramework>net48</TargetFramework>\r\n    <OutputType>WinExe</OutputType>\r\n    <UseWindowsForms>true</UseWindowsForms>\r\n    <ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>\r\n    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>\r\n    <ApplicationManifest>Properties\\app.manifest</ApplicationManifest>\r\n    <ApplicationIcon>icon.ico</ApplicationIcon>\r\n  </PropertyGroup>\r\n  <ItemGroup>\r\n    <Reference Include=\"System.Design\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <Compile Update=\"ColorButton.cs\">\r\n      <SubType>Component</SubType>\r\n    </Compile>\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ProjectReference Include=\"..\\LibSidWiz\\LibSidWiz.csproj\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.3\" />\r\n  </ItemGroup>\r\n</Project>"
  },
  {
    "path": "SidWiz/SidWizPlusGUI.resx",
    "content": "﻿<?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    The primary goals of this format is to allow a simple XML format \r\n    that is mostly human readable. The generation and parsing of the \r\n    various data types are done through the TypeConverter classes \r\n    associated with the data types.\r\n    \r\n    Example:\r\n    \r\n    ... ado.net/XML headers & schema ...\r\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\r\n    <resheader name=\"version\">2.0</resheader>\r\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\r\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\r\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\r\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\r\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\r\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\r\n    </data>\r\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\r\n        <comment>This is a comment</comment>\r\n    </data>\r\n                \r\n    There are any number of \"resheader\" rows that contain simple \r\n    name/value pairs.\r\n    \r\n    Each data row contains a name, and value. The row also contains a \r\n    type or mimetype. Type corresponds to a .NET class that support \r\n    text/value conversion through the TypeConverter architecture. \r\n    Classes that don't support this are serialized and stored with the \r\n    mimetype set.\r\n    \r\n    The mimetype is used for serialized objects, and tells the \r\n    ResXResourceReader how to depersist the object. This is currently not \r\n    extensible. For a given mimetype the value must be set accordingly:\r\n    \r\n    Note - application/x-microsoft.net.object.binary.base64 is the format \r\n    that the ResXResourceWriter will generate, however the reader can \r\n    read any of the formats listed below.\r\n    \r\n    mimetype: application/x-microsoft.net.object.binary.base64\r\n    value   : The object must be serialized with \r\n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\r\n            : and then encoded with base64 encoding.\r\n    \r\n    mimetype: application/x-microsoft.net.object.soap.base64\r\n    value   : The object must be serialized with \r\n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\r\n            : and then encoded with base64 encoding.\r\n\r\n    mimetype: application/x-microsoft.net.object.bytearray.base64\r\n    value   : The object must be serialized into a byte array \r\n            : using a System.ComponentModel.TypeConverter\r\n            : and then encoded with base64 encoding.\r\n    -->\r\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\r\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\r\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\r\n      <xsd:complexType>\r\n        <xsd:choice maxOccurs=\"unbounded\">\r\n          <xsd:element name=\"metadata\">\r\n            <xsd:complexType>\r\n              <xsd:sequence>\r\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\r\n              </xsd:sequence>\r\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\r\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\r\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\r\n              <xsd:attribute ref=\"xml:space\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n          <xsd:element name=\"assembly\">\r\n            <xsd:complexType>\r\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\r\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n          <xsd:element name=\"data\">\r\n            <xsd:complexType>\r\n              <xsd:sequence>\r\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\r\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\r\n              </xsd:sequence>\r\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\r\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\r\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\r\n              <xsd:attribute ref=\"xml:space\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n          <xsd:element name=\"resheader\">\r\n            <xsd:complexType>\r\n              <xsd:sequence>\r\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\r\n              </xsd:sequence>\r\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n        </xsd:choice>\r\n      </xsd:complexType>\r\n    </xsd:element>\r\n  </xsd:schema>\r\n  <resheader name=\"resmimetype\">\r\n    <value>text/microsoft-resx</value>\r\n  </resheader>\r\n  <resheader name=\"version\">\r\n    <value>2.0</value>\r\n  </resheader>\r\n  <resheader name=\"reader\">\r\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\r\n  </resheader>\r\n  <resheader name=\"writer\">\r\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\r\n  </resheader>\r\n  <metadata name=\"contextMenuStrip1.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\r\n    <value>122, 17</value>\r\n  </metadata>\r\n  <assembly alias=\"System.Drawing\" name=\"System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\" />\r\n  <data name=\"moveLeftupToolStripMenuItem.Image\" type=\"System.Drawing.Bitmap, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n    <value>\r\n        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m\r\n        dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAEvSURBVDhPY/j//z9FGKsgKRirICkYqyApGIVTcjyD\r\n        DYh3I4sRwigcoObbhYdTvyOLEcJwBlDzxe6zDf/bT9b8z9gR+z9pY8T/mJXB/8MW+v73n+n+33Oi43+n\r\n        Duv/1g2m/00r9P/rF2gBtUENAGo+2XWm/v/qG4v+L7827//iK7P+z7807f+s85P/Tz3T93/Cqc7/3cdb\r\n        /7cfbfzfdKj2f92Biv+amaoIA4DOPtJ2ovr/0qtz/i+8POP/3AtT/s84N+H/5NPd//tOtv/vPNb0v+Vw\r\n        3f+Gg1X/a/aV/a/YW/RfJVkRYQAIA519vnpf4f9moKKUVdE4nQ2yGaRZIU4W1QAQBvr5JtDP34GaLZDF\r\n        8WEUDlAzG1DzDmQxQhirICkYqyApGKsgKRirIPH4PwMAFjvYzdLOEMwAAAAASUVORK5CYII=\r\n</value>\r\n  </data>\r\n  <data name=\"moveRightdownToolStripMenuItem.Image\" type=\"System.Drawing.Bitmap, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n    <value>\r\n        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m\r\n        dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAErSURBVDhPY/j//z9FGKsgKRirICkYqyApGKsgLuw/\r\n        032350RHNmQxFAWEMFDzd6cO69vIYmCi5HjGfxAuPJz6P3df4v+MHbH/kzZG/I9ZGfw/bKHvf6DN/4Ga\r\n        /yctjvofMzf0v2mF/kUMA9bdWvp/9Y1F/5dfm/d/8ZVZ/+dfmvZ/1vnJ/6ee6fs/4VTn/+7jrf/bjzb+\r\n        bzpU+z9gotd/zUzVk3ADQDavvL7g/9Krc/4vvDzj/9wLU/7PODfh/+TT3f/7Trb/7zzW9L/lcN3/hoNV\r\n        /2v2lf336nH9r5KseARuAD5nA/3837rB9L9rh/3/uIWR/z27XUCaz4P0wQ0gBusXaFkAnf0dqPkmsjiK\r\n        IkIYqHmHQpws+dGIDWMVJAVjFSQFYxUkHv9nAACaytXUCmmsdAAAAABJRU5ErkJggg==\r\n</value>\r\n  </data>\r\n  <data name=\"cloneChannelToolStripMenuItem.Image\" type=\"System.Drawing.Bitmap, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n    <value>\r\n        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m\r\n        dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAMUSURBVDhPVdJZTBNRGAXgEhONPpj4YHww+iAxUYO4\r\n        LwHcQCq4hJiIJmoUgbI1kWiMEQWtBSKLCxiiJiYIKsYlRmLLYgsSKi2bgAoUFKTsa8vSZe600/Z4Z9CC\r\n        D2fm6f/uuf+MCIDoYEp1xLHsOnXY3bpJGoaGCLnHv+tJWHYtOZKpbfO/WpMbEKdYyM/8i/AITa/5YjQT\r\n        46SVdZqsLEyWuRjNfAg6hs1I+dgPSV6XzT9Oseg/4EiGdtzMOJy1vQ7U93Go7+dQ18tBa7CjuotFud4K\r\n        lgOUnQxSivsQ/bCL2StVCogAHM3UMTM2O5oGnGge4OibQ6OA2KHpZqHS2yjgQtOwE/VDDqSV9iMwqerV\r\n        XINMHeGBFgp8G+TQMugUsIY+B2p+21Hxk4FhzCpcxUI4jE3ZEHxTQzvNA6atdrTSE9poWkdc+C4gfAuH\r\n        0EJNW5S0WaD4MYNpuiexvIZ4gMMZOjJFgY4RN/R0uJ2mbdiFHxT7SndSa5hFKn8xUNM9TFkdEKfq5oBQ\r\n        Cpgo0DnqFtJBo6dY69BsEx65/FoMybMtAmSiQLB8HnAoTUeMFjv0nmGXpwmP8HuRvjyA24rTOPVkHcbN\r\n        DgTK5gHBci2ZoN+bP/XfcGLRHiS+9EP8852Iyt+K5A/heNuYiyvvwhCSuxL7kuftIFCmJaMzLNqHZmvz\r\n        kb7YheLmR3jflCcMvm7MQU7FFRTWZiGuSIwdd5ZgY6qXlwDsp9rwNEG4ksVJJcGJEoIoel9++L4qEZnl\r\n        UqSXxkCmuAB5SSyeaFJxtiAAa2+J7AIQcK2aDJgILmpcKOgE8juAM0/X401DDorq7qFQl4V8bQZFEvBY\r\n        I0dMUQi8r69yr0wSLRaA3ZeqbIYJmzuuyoXYSicknzkcf+yN0NxVCHqwAv7Zy3DumR8eVcsQ+UIMcZ4v\r\n        fCRKi2cH2+IresZnWLPBSFw9Ewx6xhl0/82ExSHE7+5ynC8MwuG8rfS3Zqc3RJS1e4DtCWrpZomqYkuM\r\n        2rQ5RkU8kajIJhpfySey5sZS9+obC+AbXTrlE1lW6RtZFgVA9AcNBAT+A6jLMQAAAABJRU5ErkJggg==\r\n</value>\r\n  </data>\r\n  <data name=\"removeChannelToolStripMenuItem.Image\" type=\"System.Drawing.Bitmap, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n    <value>\r\n        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m\r\n        dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAKHSURBVDhPhZPbS5pxGMeL3e5u/8Eu9zd4tT9hl6Oh\r\n        E4tQ8VRCgXQRIQZCJIIsCAryUHmokYoHjCIN00jRKNRqWIoaqZ0cDeq73/N7p9Qa7OJ5eQ/P9/P7Poe3\r\n        D8B/Ix6P69Pp9LdoNCr6+9uLh+cxMjIi0ul0RpvNVqjVari+vsbBwcFP9t6sVCp7oFdCCpY0wMSlcDiM\r\n        4+Nj3N3d4fHxEY1GA8wFFApFSS6XD1DuPwF6vT6xs7ODs7MzXF5eghx0Oh20223U63Wsrq6CuUhQ7gvh\r\n        2tpav8fj6V9cXLxptVpcUK1WcXV1hWaziYeHB5ycnOD09BTT09OdHmB9ff2Nz+d773a7RcvLyx8Z6CmV\r\n        SoEgFATo3lM4HA4CMOkfABN/YGKHy+WqLi0t3TAHCIVC2N3dRSwWw+bmJiKRCLa3t0F9SSQSmJqaEgBe\r\n        r/ctqylwdHTEbd7e3uL8/ByBQADMDdj4sL+/j729PS6k9xQTExMCYGVl5dPW1ha3VqlUeNNoZATrQmgS\r\n        VFImk8H8/Dz8fj/Gx8cFgN1uj5TLZZRKJZ5ITSIQAanjRqMR5I5c5HI5sL3AxsYGRkdHBcDCwkKGOk11\r\n        0gmHh4coFAoccn9/j7GxMW69C5idnQVrOtRqtQCYm5v7XiwW+ayDwWCvboIQQKvVcjiVkM1mYTabwZpO\r\n        yyQArFbrZ6qJto1OJRiJKWgPVCoVrTCSySR3ODMzwyc0NDQkAOhiMpncBKEJUN00hYuLC95Ei8XCwfRM\r\n        C5TP58HKhlQqrfQAk5OT/QaDwet0OvnJ1Eyql53yxP4JaDQaWl0MDw9jcHDwFxP/EIvF73qAbrB6v7DE\r\n        MktMymSyqEQi+fr8++tA32/nJAr+j0gx2AAAAABJRU5ErkJggg==\r\n</value>\r\n  </data>\r\n  <data name=\"splitChannelToolStripMenuItem.Image\" type=\"System.Drawing.Bitmap, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n    <value>\r\n        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m\r\n        dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJmSURBVDhPpZDrT5JhGMbZ2Pwz+OTWVhpq6qw0NchD\r\n        kUcQk0ZCQjowOSgHUWEGKYiKokmmoUieyywPo5XlAStiushm63/wu25uV+/LBsJ6P7T14bc9z3Vf9/U8\r\n        900D8F/EXVR+qU2xJTHHarHcnauwVXtL4+rRg2pH6rQFTbAGOiB7X+uINZEIZiucmrVGqFYaUDZaFK3T\r\n        1Lv1dKLZbQsa8fK3F/OHkzD79ZCs1IyJX/PpwkUuXTBT7m5ek+PZvhMje/2QLdxHkSNvjN2TQ6cpd6SG\r\n        ps26UH/QgoVfHswdTsC0qYFoiR8SLnB1NdPlhirP7ZDyTT1cew4MB3vRtFQPli07lGe5rAt/Q/5BxFBt\r\n        PMDsoRvTP8dR+6qKkONHKHEVYOibHY6AFZyBG8i1ZDFIPVxs8AkZD9/VwXswBs/BUwjnebjzoiwh2jxS\r\n        kMBxsuH42g37FwsK7fm4asw4C5CuChiy1VpM/nDh+fcn0Kw3gjfB6Y0EFA9c75XMCNHz2YxufydYj3OQ\r\n        2Zp6FiBerqbfW+Qd2XcfYXR/kJizD/KlOnCG2b7igXyf2CtA144RXX4TlMsyZOpTjtJakunRABJi01z+\r\n        VMmJbl2BwYAtPKvB1wzdmiL8qnm7HaIpATJ0KSdpzcncSF80gKRy/GZiqavQfWuIdap+2wjrbmf4Zdmi\r\n        BFltaacZOqY7VZ2UGNsTFxChsC83u3KEA8t2Bzq32lDcw0K6lplN5f1LiHDNnHVs/KRH24YWlzQXj6k8\r\n        JJQiyZWOdLR/1KJ1owVM5QVCovZRiiTEpkFsGkzFeSTJzxEStY9S/HdA+wPHj5vHF6CU6AAAAABJRU5E\r\n        rkJggg==\r\n</value>\r\n  </data>\r\n  <data name=\"copySettingsToolStripMenuItem.Image\" type=\"System.Drawing.Bitmap, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n    <value>\r\n        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m\r\n        dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJnSURBVDhPddLNT9NwHAbwHtX4J+mFiK+JMYwNb56M\r\n        MVEvusMSX9C5sSsSOanx7MVEuSBGgwy7tB1sghIofCVjsBdexlY2XNv18WmNFy2HJ78m6/fZ9/dJlXMJ\r\n        9U00bcwNZQyJZXQzljHMoRHdvJLUCn2J7J1TN94dA6AcFWUwpRX4YNasntSZasuVjYYtlZYtmam6RFPz\r\n        L07fnDjx7+DfKLG0vrLb9mRWXFHXHfmy2pWJhbZYXU+yJVvGpnfk/IOZ4bBhP0o0rZv7HU/yJVfyG67k\r\n        ftrycflQ1mpt2bZssX45cv15fjVs2E9Q0Dz05NumK8WyK0bJkexaV17O1DH+eQvPpspBbr0utm+/KnSu\r\n        jRsrtHlIm+NBwSALWoeQ7xUvSHGzJ9q6LeOfNuHxDdqANqANaAPagDagzXvanFQiKV4hKID8qPRkcasn\r\n        BW4yxn+lDWgD2oA2oA1oA9qANqDNmDLwVOMVIMs1iFn/cy6xzF+bNqANaAPagDagDWgD2iCS1DxlIGmY\r\n        e20ntIA2oA1oA9qANpha6uBt0UKZ17k6YnhK5Mm8VJqN/wpGWUAb0CUIbUAbZKUbbFJp9TDkF1y+n1vY\r\n        sVypNvnx7NtSazpSbzky+oFXCAoA2oA2oA30Dd/DRtXyEEuz4GJCnbyUyM31x3Nr/XF1kSejLtKGVwC4\r\n        EbhZcHIzXgnQSwh+i6Z0L/Tj8EMbhzZHFjQ6DqJJbhA27Ic2oM2RBdVWA7HHc+HDfi4k1F3agDagDWgD\r\n        2gTZZvYOehgc1rqhw3767s5O98e/btHEo4nFk/FP1Tobzx2cuTdbjTzSmr8B1uo6cuIIh6sAAAAASUVO\r\n        RK5CYII=\r\n</value>\r\n  </data>\r\n  <data name=\"pasteSettingsToolStripMenuItem.Image\" type=\"System.Drawing.Bitmap, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n    <value>\r\n        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m\r\n        dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAKqSURBVDhPdZPLTxNRFIfdmvhHuHbhwo0bXYjgQiMh\r\n        qRQxEk2MotEoxjbRGAME1BIVgooQUcDEmkCkkPJICBoDKa+WAqXQ57SUATptebSdoWCnM/Pzzq0YTMri\r\n        y7nJPec75965cwgApeXG0WME5CCxl5MLtfAEwawmf9WfhBIfhOCvoqjrL/eP74mMBE0uQSg09V7mWROW\r\n        PhdC3hpAYkFPUdfB1guI2uoRsb7hSa6QSwB+uRfszycItOSDaSZ8OEvIA9OUB3/TGSy0aRAer6OT5BQk\r\n        A10IDemwNFiBQN89MOa7qO3xotrkRuX3LBUd03jYbkd5Qx+0BpvrVLn58D9B3GdEcOABmN7b8HZeJ1xD\r\n        dbcbCsmICjJihAgvYSUuguNFGIZjKK2b4U7f6T9CBZuuNtqV6bkFX1dWoE6wmVIwFpQwEcpglEmj35mC\r\n        kFZgYUW8G9lAicHGUMH6fAt8ppvwkmLPtzK4jVepILGjwM5KsK9ImFwS8cO7i0A0hXVBhPA7A02NVaGC\r\n        qL2RdvUYy+DquAJXeyk5ggfJXQXzaxIcqxKm2QwsgTSG3TvocQhYJce5/HI6K+CmXtGuix2lcH4qgfOj\r\n        ll4cvwsscgrFsSbDGhJhCabpJBwvo2RPELYYaFdnqxaOZg0cTRoqSFAB4OJkLIRlzJFJbCvqfYiICAq0\r\n        L/4K2F+1tKtaPPu2CLONRXjW6SJHALxRwB/LRjeRza8BNhZ0r/i5LStYHqqiXedI8Ux9IeyvL1LBVipz\r\n        oCC+k0FxTXaCUU+3DgHzUzCmx+Qz6uHr1BGBD1wyfqAgwsehrZ6hf+F5wqw6yX7KG6awIUiIJMnjSYiI\r\n        JjOI8VnWCVvbMi5VWdP/vev95D0aGynQj4cL9JNKgX5CIJGgxgnhnH5yO183FtFUWpN/AGPgB8LRi7cv\r\n        AAAAAElFTkSuQmCC\r\n</value>\r\n  </data>\r\n  <metadata name=\"toolStrip1.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\r\n    <value>17, 17</value>\r\n  </metadata>\r\n  <data name=\"AddFileButton.Image\" type=\"System.Drawing.Bitmap, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n    <value>\r\n        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8\r\n        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIQSURBVDhP3Y/NaxNBHIbn4L03/wQP1tSPtFHx4EUQQfAi\r\n        elBPImgJKBEKilBzSBsRUkqjBxGLWCrYWvEDQTwsnhpb202btdkmaW3aGrrBZr+/N/s6267SePAqOMzD\r\n        zBye9/0N+U/WwrtovvrmtLP6B+X3R5YqLzt2L72ItrXwMdrGMMd3hTohuWcxy/ebAHy6A7bvrlnHOtNt\r\n        r01esH5Rm7luVicTVf71oXOhTgj3qkvyPRPWegb68iD0lWEY1XH4rkizNPieSpG3324DgiDY/ESMC3VC\r\n        SqMHlaYjUzkLrTIE5WsSm1PdqE/HIRZSEOfvQszfRoPtoSToZAJKE51qqNOAsS7HNTYgclk0ZtMQPt2E\r\n        IXymrUrYHpzS7wmC77Fj+81QDyY47Gz9v2lRDMDTt0TP4GHVsjDXUjC+9UAvX4XGX4SnFcGO7AzIxmzf\r\n        M+ApObjiBzg/xmFvPKbifRgrd2BU4tAXL0FbOAO1cAKemgc7sCMg9yhmNR0FzuZb2MIIbX0Ac5W2Lget\r\n        V6AVz0PlTkGZOwZ59gBc5UtrQPDwHZHKT2F9H4RZTVI5Ab10mcpnaetJKPmjkGc6IE3voQFTrQGL9yI1\r\n        Q+Ikhnne1MpPoJYeQuUzUIt9ULheKIVbkOcTkPNxyOw1DCR7fS4TlUKdEL6v/Qaf3jfK9e+ts+mI+Vf6\r\n        I+ZcOiLzqfahUP+ni5Cf0Q/maNiUUDkAAAAASUVORK5CYII=\r\n</value>\r\n  </data>\r\n  <data name=\"RemoveLabelsButton.Image\" type=\"System.Drawing.Bitmap, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n    <value>\r\n        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8\r\n        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADlSURBVDhPYxgFDAwnT578TwkGG/Dnzx+y8IsXLxAGkGPQ\r\n        EDPg0fop/48naf3f48z6/2CU/P87y9pRDcCHQZovldn+/7619/+/azv+f11R9P9Mrv7/Kwvb/zOATCGE\r\n        90cp/f8G1Px/ku///+WC//+3Kf5/3eXwf3+k4n9oasAPGp1Y/v07u+4/MnhfL/EfJA5Vgh/sdGF+/GVu\r\n        8v//QE0/yhj+vwPihxnM/3e4Mj+BKsEPDodLNpyMl//9rFzx/6tK1v93Uxj/79Vi+bPdhakKqoQwOBwu\r\n        W7HTmfk+yNkgF0E0MzAAAFJ7DMf5gwrhAAAAAElFTkSuQmCC\r\n</value>\r\n  </data>\r\n  <data name=\"RemoveButton.Image\" type=\"System.Drawing.Bitmap, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n    <value>\r\n        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8\r\n        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAJ9SURBVDhPjZPLS2JxFMfvMNvZzX8wy/kbWs261SyHDAYi\r\n        KqUXCglSGxEDISpBXAhJYqUy2WBGKYohhj3IqEh8NWhKJvbyMThQ3znnd6/TYpjHgcO993fP+ZznT/of\r\n        icfjuoODA3soFOpRjv4tk5OTPaQmq9Waub6+xsPDA46Pj7/TmWVkZOTvoImJib7x8fHczs4O0uk0ms0m\r\n        np6ecHNzg3A4jOHh4Rxpn2L+u+h0ugSljcvLS9RqNXAG7XYb9/f3qFar8Hq90Gg0CcX8RQC8YnU6nY93\r\n        d3fCoVKpoF6v4/b2Fp1OB/l8HoVCATMzM23FTZI8Hs9rn8/3jp49a2trH+jnMzUNDGFlQPed1e12Y3Z2\r\n        Foq7JPn9/vcEcK+urlZcLtcjZYDt7W3s7e0hEokgGo2CJoDd3V1wXxKJBIxGowygqG8oavDi4kKk2Wg0\r\n        UCqVEAwGQec4PDzE0dER9vf3hSOfs05PT8sAasjHWCwmUiuXy6JpPDKGdSE8CS4plUrB4XBgc3MTer1e\r\n        BiwvL4eKxSJyuZww5CYxiIHccZPJBM6Oszg9PYXNZkMgEIBWq5UBS0tLKe4018kRzs/PkclkBKTVamFq\r\n        akqk3gXMz89jY2MDY2NjMsBut3/NZrNi1ltbW7/qZggDaKkEnEs4OTmBxWLB+vo61Gq1DFhcXPzENfG2\r\n        cVSGsTMr78Ho6CivMJLJpMhwbm5OTGhwcFAGsJjNZh9DeAJcN0/h6upKNHFhYUGA+ZsX6OzsDFQ2VCpV\r\n        WXGXN9BgMHxZWVkRkbmZXC9FeabLA7oXvLoYGhpCb2/vj/7+/m8EeKu4vwgZqsiwSIbJgYGB8GcS5dcf\r\n        RJJ+Ao18U3cRbJU0AAAAAElFTkSuQmCC\r\n</value>\r\n  </data>\r\n  <data name=\"LoadButton.Image\" type=\"System.Drawing.Bitmap, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n    <value>\r\n        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m\r\n        dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAILSURBVDhPpY9LaxNRGIa7cO/On+CqFdFmRHQpiCs3\r\n        ohtvIAW1WUiUQkXQLnoBIVAaRUQshVLBFotGBJfWRTW17dhEM5mkteklNMFm7pecubx+mWomQhBLYZ75\r\n        zjnwPu85bQD2RMvD3dDycDcEv9zbTr44c5mtfbzaxBWWf3dyWXjTfoDY/xfJgH0NgZjkbN/3aOnTV2dn\r\n        7VgVbMx219Znu+0/lOZ7rc3UnWLu9ZHzoWA6IvuuBXsjDmNlGMbqKMziFHxHIpcO39UIZWfvVAGP1cRp\r\n        LhMKXnKqxxQKJ6AXRqB+68N26iYqc1FI6X5ISw8g8XdRXewhYnSzMsRXnVoomIwwx9yClEmgujCE8ofb\r\n        MMufqVX93V6fcngDep44yVmhYOIYC97v2YQJuEYQdE0BdikBa70f5o8eGPnr0IWLcPUsxPFIk2CMq/mu\r\n        CVf9BEd6D/ZzCrWtZxR8CHP1HsxCFEbuEvTvZ6GlT8HVeIijzYKnnO0xFWw7iVp5nFofwVqj1pV6axf0\r\n        7AVomTNQv56AsnAYjvoF4pNmQSJi+Uyi8BjszWFYxT4Kx2CI1yh8jlpPQ+WPQ5k/BHnuIAlSqGcaglz8\r\n        aInJBVlffuHp+efQxMfQhDi07ADUzH2o6V4oSzEofBTK4g3YlRmfMnJDIAy03xKGOiaEwY4KTeufDAZT\r\n        ocxIQ7AXWh7+P2j7BY3RGzIVTOkAAAAAAElFTkSuQmCC\r\n</value>\r\n  </data>\r\n  <data name=\"SaveButton.Image\" type=\"System.Drawing.Bitmap, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n    <value>\r\n        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m\r\n        dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAKWSURBVDhPjZJbTxNBGIb7l4AunhJ/hdeGU4FSMICI\r\n        nDSaeKFGkGg0QhG0ECggimIBu4AGRShULBhMORTaIpQCPUN32bb7+s1UuSKEiyeTbud9vnd3RpNtGKsg\r\n        mgk7ESSUbIOoEsguSXOhdAyXro3jYpmI8wYxdLlcnBP0w1cAaJjAuOCOY8EtQU6okIj4UQrhwyR2wwoC\r\n        0QT/LSkp7EUS2PDL6rhjP3H1gW1f0FvymcC5sBHHnT4/4gqFaWPBoxkEYwnsUiAaT3JxhFbv/hFWfTKs\r\n        jiBIAmoiM0HYQYLb5h2+icEE/oiC4EGCT2YNdqiNa0fG6raEuk4Pvi6FIOhHVCZQ7K5DEvj4JkZhow1+\r\n        WmNyCjK1Ch0kqTqbLmF5S0KNyY3eSR+E4mGQQFRtawe41eODhyoyNgNHND3Jp8ekFLYCynHYuRXHzVfr\r\n        6Jr4A23RRy7A9DIJurd5dR1NL2qyofTJHMoIffMsdPSc/fefqrYVdFg90BYOkYCOacoZRX3XJr0bQWt9\r\n        lxf1nQwPak0emriB6o51VLW7UNm2iqqXa2i1uKDVvU8LJpei6TCRDhIkYeGa125e+UY7CSh43bhCLOPZ\r\n        4AqyCt6lBROLYQp7/+E5ns7DNLm6w8XDlRSsaHGivOU3mvqXkJU/wARWiI4Q6kxeft5noez5LzzsWURm\r\n        Xj80dDUxOh/idSU6svtf8k6F3YmSpz9xr3Membl9JCgVMWwPUoO0YHLt86kwQXHzD9xtt5PADM05g1Ud\r\n        mg2QwH3mBrrGWTQYbcjI6VE1Qol1feD7Hgamdk9835NoaJ1B7YtpJgiwj/hY0H+yCPrRoFA8QtfTwm+Y\r\n        tvADsnSDdFRvkZn/hj5YH1XupZCZT87I6Y5k5Jq//QWbzBTt9kMQwwAAAABJRU5ErkJggg==\r\n</value>\r\n  </data>\r\n  <metadata name=\"imageList1.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\r\n    <value>277, 17</value>\r\n  </metadata>\r\n  <data name=\"imageList1.ImageStream\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\r\n    <value>\r\n        AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w\r\n        LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0\r\n        ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAAB0\r\n        HgAAAk1TRnQBSQFMAgEBBwEAATgBAgE4AQIBEAEAARABAAT/ASEBAAj/AUIBTQE2BwABNgMAASgDAAFA\r\n        AwABIAMAAQEBAAEgBgABIP8AFwADTAGPA1YBtQJiAV4B7gG6ATwBFgH/AbYBOgEUAf8BtAE5ARQB/wGx\r\n        ATcBEwH/Aa0BNQESAf8BqgE0ARIB/wGoATIBEQH/AacBMQEQAf8BgAF4AVcB/gJlAV4B8QJbAVoBxAgA\r\n        A0oBigNcAcMDXgHwA14B8ANeAfADXgHwA14B8ANeAfADXgHwA14B8ANeAfADXgHwA14B8ANMAZAEAANQ\r\n        AZsBwwGOATgB/wHAAYsBNgH/Ab4BiAE0Af8BuwGFATEB/wG5AYMBLwH/AbYBgAEuAf8BtAFOASwB/wGy\r\n        AUwBKgH/AbEBSwEoAf8BrgFJAScB/wGtAUYBJgH/AasBRQEkAf8BqQFDASMB/wGpAUEBIQH/A1ABm0QA\r\n        AmEBWwHeMP8DXgHtCAADYgHhAfEB7wHtAf8B9wHzAfEB/wH4AfQB8QH/AfgB9AHwAf8B9wH0AfAB/wH3\r\n        AfMB8AH/AfcB8wHvAf8B9wHzAe8B/wH3AfMB7wH/AfcB8wHvAf8B+AHzAe8B/wHyAe8B6wH/A2IB7wQA\r\n        AcgBkgE8Af8DIgH/AyMB/wMkAf8DJQH/AyYB/wMnAf8DJwH/AygB/wMpAf8DKgH/AysB/wMsAf8DLAH/\r\n        Ay0B/wGpAUIBIQH/RAACgAFiAf4E/wGvASoBAAH/Aa0BJwEAAf8BrQEnAQAF/wHwAeUB3gH/AfAB5QHe\r\n        Af8B8AHlAd4B/wHwAeUB3gH/AfAB5QHeAf8B8AHlAd4F/wGAAXkBWAH+CAADXgHwAfYB8QHtAf8BvwHc\r\n        AcIB/wG/AdwBwgH/Ab8B3AHCAf8BvwHcAcIB/wG/AdwBwgH/Ab8B3AHCAf8BvwHcAcIB/wG/AdwBwgH/\r\n        Ab8B3AHCAf8BvwHcAcIB/wH3Ae0B5gH/A14B8AQAAcoBlAE+Af8DHgH/Aw0B/wMNAf8DDgH/Aw8B/wMR\r\n        Af8DEgH/AxMB/wMUAf8DFQH/AxYB/wMWAf8DGAH/AyoB/wGqAUMBIwH/RAABxAGGAR0F/wGvASoBAAH/\r\n        Ac0BnwE3Af8BrQEnAQAF/wHwAeUB3gH/AfYB7wHrAf8B9gHvAesB/wH2Ae8B6wH/AfYB7wHrAf8B8AHl\r\n        Ad4F/wGoATQBEgH/CAADXgHwAfcB8QHtAf8BvwHcAcIB/wG/AdwBwgH/Ab8B3AHCAf8BrwHTAcUB/wGc\r\n        AcgByQH/ATEBrwHRAf8BugHZAcMB/wG/AdwBwgH/Ab8B3AHCAf8BvwHcAcIB/wH2Ae0B5gH/A14B8AQA\r\n        AcwBlwE/Af8DGwH/AwgB/wMJAf8DCgH/AwwB/wMNAf8DDwH/Aw8B/wMRAf8DEgH/AxIB/wMUAf8DFQH/\r\n        AycB/wGsAUUBJAH/RAABxQGGAR4F/wGvASoBAAH/Ac0BoAE4Af8BrQEnAQAF/wHwAeUB3gH/AfYB7wHr\r\n        Af8B9QHsAeYB/wH1AewB5gH/AfYB7wHrAf8B8AHlAd4F/wGsATUBEwH/CAADXgHwAfcB8QHsAf8BEQGq\r\n        AT0B/wEPAagBOgH/AQ0BpgE3Af8BAAE+AZoB/wEXAZ8B0wH/ARcBnwHRAf8BAgGGAa8B/wEDAZoBKgH/\r\n        AQEBmAEoAf8BAAGWASYB/wH2Ae4B5gH/A14B8AQAAc8BmgFCAf8DFwH/AwMB/wMEAf8DBgH/AwcB/wMJ\r\n        Af8DCgH/AwsB/wMMAf8DDgH/Aw8B/wMQAf8DEQH/AyUB/wGtAUgBJgH/RAABxwGIAR0F/wG0ATEBAAH/\r\n        AdABpQFBAf8BsgEvAQAF/wHwAeUB3gH/AfYB7wHrAf8B9QHsAeYB/wH1AewB5gH/AfYB7wHrAf8B8AHl\r\n        Ad4F/wGyATkBFAH/CAADXgHwAfcB8gHtAf8B6AG9AaEB/wHnAbsBnwH/AdABswGSAf8BIQGCATkB/wEH\r\n        AY4BhgH/AQQBiwGHAf8BGQGDAYAB/wHHAaYBggH/AeABrgGOAf8B3gGsAYsB/wH2Ae4B5gH/A14B8AQA\r\n        AdEBnAFDAf8DEgH/AwAB/wMAAf8DAQH/AwMB/wMEAf8DBgH/AwYB/wMIAf8DCgH/AwsB/wMNAf8DDQH/\r\n        AyIB/wGwAUoBKAH/RAAByAGKAR4F/wG3ATkBAwH/AdEBpgGDAf8BtgE2AQAF/wHwAeUB3gH/AfYB7wHr\r\n        Af8B9gHvAesB/wH2Ae8B6wH/AfYB7wHrAf8B8AHlAd4F/wG2ATsBFgH/CAADXgHwAfcB8wHuAf8B5wG7\r\n        AZ8B/wHRAbYBkwH/AZ8BqgE7Af8BMgGyAYcB/wEoAb0BigH/ASQBuwGHAf8BLgGrAT4B/wGRAZMBJwH/\r\n        AcUBogFAAf8B3QGpAYgB/wH2Ae4B5wH/A14B8AQAAdQBngFFAf8DDQH/AwAB/wPRAf8DrQH/AwAB/wMA\r\n        Af8DAQH/AwIB/wMEAf8DBgH/AwcB/wMIAf8DCgH/Ax4B/wGyAUwBKgH/RAABygGMAR8F/wG8AUIBFwH/\r\n        AboBQAEPAf8BugE/AQ0F/wHwAeUB3gH/AfAB5QHeAf8B8AHlAd4B/wHwAeUB3gH/AfAB5QHeAf8B8AHl\r\n        Ad4F/wG5AT8BFwH/CAADXgHwAfcB9AHvAf8B5gG5AZ0B/wG2AbMBhwH/AYgBwgGTAf8BJgHFAY8B/wEW\r\n        Ab4BgAH/ARIBugE9Af8BGwG9AUIB/wE7AbABPwH/AaMBlgEtAf8B3AGnAYQB/wH2Ae4B5wH/A14B8AQA\r\n        AdUBoAFGAf8DCQH/AwAB/wMAAf8D3gH/A0QB/wMAAf8DAAH/AwAB/wMAAf8DAQH/AwIB/wMEAf8DBQH/\r\n        AxsB/wG1AU4BLAH/RAACgAFnAf4w/wG8AUEBGAH/CAADXgHwAfcB9QHwAf8B5QG4AZsB/wGhAbMBQgH/\r\n        AUABzQGgAf8BIQHFAZAB/wEZAcABhwH/ARUBvgGBAf8BFQG8AUEB/wEvAb0BhwH/AYkBkAEhAf8B2gGk\r\n        AYEB/wH1Ae8B5wH/A14B8AQAAdgBogFJAf8DBAH/AwAB/wPPAf8DqAH/AwAB/wMAAf8DAAH/AwAB/wMA\r\n        Af8DAAH/AwAB/wMAAf8DAQH/AxYB/wG3AYEBLgH/RAADTQH6BP8B7QHEAZsB/wHtAcQBmwH/Ae0BxAGb\r\n        Af8B7QHEAZsB/wHtAcQBmwH/Ae0BxAGbAf8B7QHEAZsB/wHtAcQBmwH/Ae0BxAGbAf8B7QHEAZsF/wG+\r\n        AYEBGgH/CAADXgHwAfcB9QHyAf8B5AG3AZkB/wGjAbYBgAH/AYIB0AGnAf8BKAHJAZgB/wEgAcYBkQH/\r\n        ARwBwgGLAf8BGwHBAYcB/wE0AcIBjgH/AYwBkgEiAf8B2QGiAUAB/wH2AfAB6AH/A14B8AQAAdkBowFJ\r\n        Af8DBAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAQH/AxYB/wG6\r\n        AYUBMAH/RAADXgHwBP8B7QHEAZwB/wH0AdoBwQH/AfQB2gHBAf8B9AHbAcIB/wH0AdsBwgH/AfQB2wHC\r\n        Af8B9AHbAcIB/wH0AdsBwgH/AfQB2wHCAf8B7QHEAZsF/wKAAWIB/ggAA14B8AH3AfUB9AH/AeMBtQGX\r\n        Af8BuAG3AYcB/wGTAcsBoQH/ATcB0gGoAf8BKgHLAZsB/wEmAcgBlwH/AS0ByQGYAf8BgwG7AYsB/wGl\r\n        AZkBLQH/AdcBnwE9Af8B9wHwAekB/wNeAfAEAAHbAaQBSgH/AwEB/wMCAf8DAwH/AwQB/wMFAf8DBgH/\r\n        AwcB/wMJAf8DCwH/AwwB/wMNAf8DDwH/AxEB/wMTAf8BvQGHATMB/0QAA18B2AT/Ae0BxAGcAf8B7QHE\r\n        AZwB/wHtAcQBnAH/Ae0BxAGbAf8B7QHEAZsB/wHtAcQBmwH/Ae0BxAGbAf8B7QHEAZsB/wHtAcQBmwH/\r\n        Ae0BxAGbBf8DXAH4CAADXgHwAfQB8wHyAf8B4wG0AZUB/wHQAbQBjQH/AakBtQGAAf8BkwHMAaEB/wGE\r\n        AdEBqgH/AYIB0AGmAf8BiwHCAZQB/wGaAZ4BLAH/AcMBnQE2Af8B1gGdAToB/wH3AfIB6wH/A14B8AQA\r\n        AdwBpwFLAf8B2wGkAUoB/wHaAaMBSQH/AdgBogFJAf8B1wGhAUgB/wHVAZ8BRgH/AdMBngFEAf8B0QGc\r\n        AUMB/wHPAZoBQgH/Ac0BlwFAAf8BywGVAT4B/wHJAZQBPAH/AccBkQE7Af8BxAGPATkB/wHDAY0BNwH/\r\n        AcABiwE2Af9EAANQAZsw/wHWAaoBiQH/CAADYwHpA4AB/gL0AfMB/wH3AfUB9AH/AfcB9QH0Af8B9wH1\r\n        AfQB/wH3AfUB9AH/AfcB9QH0Af8B9wH1AfQB/wH3AfUB9AH/AfcB9QH0Af8B9wHzAewB/wHyAe8B6QH/\r\n        A2IB7wQAAqgBhQH9AfEB3AHOAf8B6gHBAaAB/wHoAbkBkgH/AegBuQGSAf8B6AG5AZIB/wHoAbkBkgH/\r\n        AegBuQGSAf8B6AG5AZIB/wHNAcgBxQH/AegBuQGSAf8BzQHIAcUB/wHoAbkBkgH/ARQBNAL/AegBxAGn\r\n        Af8BqAGQAUAB/UQAA0ABcQNMAZADXQHMAc4BkwEiAf8CgAFpAf4BzQGSASIB/wHMAZABIgH/Ac0BkgEk\r\n        Af8BzQGTASYB/wHLAZEBJAH/AmIBWQHvA1wBywHYAa4BiwH/AdcBrAGLAf8IAANLAY0DYAHoA14B8ANe\r\n        AfADXgHwA14B8ANeAfADXgHwA14B8ANeAfADXgHwA14B8ANeAfADTAGPBAADWgHCA2UB9AHcAacBSwH/\r\n        AdwBpgFKAf8B2gGkAUoB/wHYAaIBSQH/AdgBoQFJAf8B1QGgAUYB/wHUAZ4BRQH/AdIBnQFDAf8BzwGa\r\n        AUIB/wHOAZkBQAH/AcsBlgE/Af8ByQGUATwB/wNlAfQDWgHC/wCJAANbAdgDNAH6A10BzBgAA1wB2QMA\r\n        Af8DXQHcCAADDwEUA0gBhANSAaMDVgG2A10ByQFhAl8B2gNhAesCTQFEAfoBlQEkAQAB/wGaASwBBAH/\r\n        AaMBNwESAf8BrQGCASIB/wFeAlwB2QMkATSQAANNAfoDOgFhAwAB/wMaAf8DGgH/AxoB/wMaAf8DGgH/\r\n        AxoB/wMAAf8DMgFPA1kB7wgAAz0BaAG4AZABMAH/AdYBugGjAf8B3wHGAbMB/wHnAdQBwwH/Ae4B3wHT\r\n        Af8B9QHqAeIB/wH7AfQB7wH/Af0B+gH2Av8B/gH9Af8B+wHrAd8B/wH7Ae8B5gH/AcEBnQGAAf8DQAFv\r\n        DAADKwFCA10BzANbAc0DTwGZAxMBGkAAAzgBWwNhAeYrAAH/A1kBxgNMAfUBeAKAAf4DgAH+AX0CgAH+\r\n        AWgCgAH+AV8CgAH+AVoCgAH+A1MB9ANRAaQDAAH/CAADSgGJAccBpAGFEv8B/gH8Af8B/gH7AfcB/wH+\r\n        AfcB8QH/Af4B9gHxAf8B5AGuAYYB/wH6AegB2wH/Ac4BrwGVAf8DSgGLCAADPwFtAzUB/wOFAf8DigH/\r\n        AyYB/wMAAf8DPwFsCAADKwFCA10BzANbAc0DTwGZAxMBGhgAAwIBAwNJAYUBYAJiAfYBIwInAf8UAAM9\r\n        AWgDEwEaDAADWwHQAz8BbANfAdUBEgHLAR8B/wEaAdcBPQH/AQUB0QEtAf8BMwHHASEB/wG2AbgBEwH/\r\n        AcsBrgECAf8DXgHXAz8BbANbAdAIAAM2AVcCYgFIAfYBtAGFARwB/wHZAaUBPgH/AdgBngEyAf8B1wGb\r\n        AS0B/wHYAZYBJgH/AdYBkgEgAf8B1AGPARwB/wHTAY4BGgH/AeIBnQEsAf8B+gHjAdEB/wHYAbsBogH/\r\n        A1EBnggAAz4BagORAf8DqQH/A64B/wOrAf8DiwH/Aw0B/wMrAUEDPwFtAxYB/wMxAf8DOQH/AxEB/wMA\r\n        Af8DPwFsBAADJwE6AycBOgMnAToDJwE6A2EB3AEsAjAB/wE1AjoB/wEjAicB/xAAAkYBRwGBAgAB7QH/\r\n        A1wBywwAA10B3wNZAcMDWwHQARIBxAEIAf8BDgG6AQAB/wHYAb0BIwL/AboBJQL/AbkBKAH/AdsBuwFA\r\n        Af8DXgHSA1kBwwNdAd8IAAMPARQCVgFUAasB1QGuAYwB/wH9AfAB5QH/AfcBxwGiAf8B9wHPAa0B/wH5\r\n        AdUBtQH/AfgB2gG9Af8B+AHeAcIB/wH6AeEBxgH/AfoB5AHMAf8B/QH1AewB/wHiAc4BugH/A1YBswwA\r\n        A0ABcAOJAf8DngH/A7AB/wOsAf8DOAH/A1IBoAM+AWoDNgH/A5EB/wOYAf8DlgH/AzkB/wMAAf8DKwFB\r\n        ATQCNwH/ATICNgH/AS4CMgH/ASwCMAH/ASwCLwH/ARMCFAH/ASICJAH/ASQCKAH/DAADPgFqAx8BLAIA\r\n        AeYB/wECAQAB7QH/Az4BawgAA18B2gNRAaIDXQHPAUIBvwEAAf8B3QHFASwC/wHCATMC/wG/ASoB/wGu\r\n        AcsBrAH/ASsB4AH5Af8DWwHQA1EBogNfAdoMAAMdASgBtwGGARkB/wL+Af0B/wH6Ad4BwgH/AfoB3AG/\r\n        Af8B+QHbAcAB/wH5AdsBwAH/AfkB3QHBAf8B+gHbAcAB/wH6AdwBwwH/Af0B6wHeAf8B7AHcAc0B/wJd\r\n        AVsByhAAAysBQgNgAeMDNgH/A4kB/wOdAf8DXgHtBAADQAFwAy4B/wOFAf8DmgH/A5cB/wMiAf8DUgGg\r\n        ATsCPgH/AZkCmgH/AToCOwH/ATECMgH/ARMCFAH/AwIB/wEgAiIB/wEmAioB/wgAAz0BaQEYARAB7QH/\r\n        A0QBeAFAAT8BQAFuAQgBAAHtAf8CWwFfAdgIAANdAdQDRwGBA10BzAH4AcYBMQL/AcgBOgL/AcUBNQL/\r\n        AcQBLwH/AZgB2AHPAf8BmQHkAesB/wNdAc4DRgGBA10B1AwAAxYBHgG5AYYBFQH/Af4B/AH5Af8B+QHc\r\n        Ab8B/wH4AdsBvwH/AfgB3AHAAf8B+QHbAcIB/wH5AdwBwQH/AfkB3QHCAf8B+gHdAcMB/wH7AecB1AH/\r\n        AfUB7QHiAf8CYQFbAeQcAAM0Af8DsQH/AwwB/wgAAysBQgNgAeMDFAH/Ay8B/wOHAf8DXgHtAYECgwH/\r\n        AZkCmwH/AygB/wMiAf8DFgH/AwwB/wEqAiwB/wEpAiwB/wQAA14B7QMsAUMBHQETAekB/wESAQMB4QH/\r\n        BAABBQEAAeEB/wIAAeEB/wgAA14B4gNbAdADKwH/AxoB/wMaAf8DGgH/AxoB/wMaAf8DGgH/A0gB9gNb\r\n        AdADXgHiDAADBwEJAbkBhQEOAf8B/gH7AfcB/wH5AdwBwQH/AfgB3AG/Af8B+AHcAb8B/wH4AdsBwAH/\r\n        AfkB3QHAAf8B+QHdAcAB/wH5Ad0BwwH/AfsB4gHLAf8B/AH5AfUB/wJfAVEB+wQBGAADNwH/A7gB/wMR\r\n        Af8UAAMSAf8DmgH/AwAB/wGFAocB/wGlAqYB/wOHAf8DhQH/A0AB/wM1Af8BhAKFAf8BLAIwAf8EAAJd\r\n        AWEB3AMsAUMBJAEcAekB/wEaAQ0B4QH/BAABDQEAAeEB/wEGAQAB4QH/CAADXQHUA0cBgQNbAc0BNQHi\r\n        AZ4B/wE/AeMBpAH/ATsB4wGgAf8BJgHgAZUB/wEdAeEBlQH/ARgB4gGTAf8DWwHQA0YBgQNdAdQQAAJq\r\n        AUcB+QH8AfYB8AH/AfkB3wHHAf8B+QHcAb0B/wH6AdwBvwH/AfoB2wHBAf8B+gHdAcMB/wH6Ad0BwgH/\r\n        AfkB3QHEAf8B+wHgAckC/wH8AfoB/wHBAY0BFgH/AwwBDxgAAzwB/wO8Af8DFwH/FAADEwH/A6EB/wMA\r\n        Af8BiAKLAf8BwgLDAf8DvAH/A7oB/wOeAf8DgwH/A5EB/wExATQBNQH/CAADOwFkAS4BKAHsAf8DRAF4\r\n        A0ABbgEdARYB7QH/AlsBXwHYCAADXwHaA1EBogNdAc8BEgHLAR8B/wEaAdcBPQH/AQUB0QEtAf8BMwHH\r\n        ASEB/wG2AbgBEwH/AcsBrgECAf8DWwHQA1EBogNfAdoQAAJhAV8B2gH1AecB2AH/AfoB5QHSAf8B+QHa\r\n        AbwB/wH5AdsBvAH/AfoB2wG/Af8B+gHdAcEB/wH6Ad0BwQH/AfkB3QHEAf8B+wHhAcgC/wH9AfsB/wHI\r\n        AZQBGgH/Aw4BEhgAA0IB/wPBAf8DHAH/FAADGQH/A6UB/wMAAf8BiwKNAf8BkQKSAf8BjwKRAf8BjwKQ\r\n        Af8BoQKjAf8DqQH/AaECogH/ATgBOgE7Af8MAAM7AWUDHwEsASYBHQHmAf8BJAEeAe0B/wM+AWsIAANd\r\n        Ad8DXAHDA1sB0AESAcQBCAH/AQ4BugEAAf8B2AG9ASMC/wG6ASUC/wG5ASgB/wHbAbsBQAH/A14B0gNZ\r\n        AcMDXQHfEAADWQG7AfAB2QHBAf8B+wHtAeEB/wH5AdoBwAH/AfkB3AHCAf8B+QHeAcUB/wH6AeABxwH/\r\n        AfoB4gHKAf8B+gHiAc0B/wH6AeUB0AL/Af4B/QH/AcsBjwEdAf8CZQFeAfEDLQFFFAADhQH/A8UB/wMg\r\n        Af8UAAMgAf8DqgH/AwAB/wMnAToDJwE6AycBOgMnAToDYAHbAZsCnQH/Aa0CrwH/AT8CQgH/EAADRAF7\r\n        ATABLAHsAf8DXAHLDAADXQHRA0gBhANgAdQBQgG/AQAB/wHdAcUBLAL/AcIBMwL/Ab8BKgH/Aa4BywGs\r\n        Af8BKwHgAfkB/wNcAdYDPwFsA1sB0BAAA1IBpAHtAdABsgL/AfYB8AH/AfoB4QHKAf8B+wHjAcwB/wH7\r\n        AeMB0AH/AfsB5gHTAf8B+wHpAdUB/wH8AekB2AH/AfwB6gHbA/8B/QH/AdIBnQE0Af8B7gHZAcEB/wJl\r\n        AV8B5RQAA4oB/wPKAf8DIQH/FAADHgH/A68B/wMDAf8QAAMCAQMDSAGDA2IB9gGBAoMB/xQAAzsBZAMT\r\n        ARoMAAMvAf8DXgHtA00B+gNiAfYDYgH2A2IB9gNiAfYDYgH2A2IB9gNNAfoDXQHMAwAB/xAAA00BkgHr\r\n        AcoBpQL/Af0B+wH/Af0B6QHVAf8B/QHrAdgB/wH9AeoB2wH/Af0B7QHfAf8B/QHwAeIB/wH9AfEB5AH/\r\n        AfwB8AHkBf8B4AGgATMC/wH7AfkB/wHfAbgBhwH/FAADkAH/A80B/wOEAf8DHAH/AxMB/wMQAf8DDAH/\r\n        AxkB/wNCAf8DtAH/AwkB/xgAAzYBWQNlAeUoAANcAfgDUAGbAywB/wMaAf8DGgH/AxoB/wMaAf8DGgH/\r\n        AxoB/wMQAf8DSwGNA18B1RAAA0gBhAHrAcYBmgX/AfwB7wHiAf8B/QHwAecB/wH9AfEB6wH/Af0B9QHu\r\n        Af8B/QH4AfEB/wH9AfoB9wL/AfwB+gX/Af4B+wH3Af8B9AHaAcAB/wNdAeoUAAOTAf8D0QH/A84B/wPL\r\n        Af8DyAH/A8YB/wPCAf8DvwH/A7wB/wO5Af8DDQH/SAADYAHUA2AB8wNeAd0YAANcAdYDRwH5A14B1xAA\r\n        Az8BbQHqAcABjBH/Af0B+QH0Af8B+wHzAeoB/wH4AesB2QH/AfgB5gHTAf8B9QHfAcYB/wHpAcsBpgH/\r\n        A14B7QM4AV0UAAOcAf8DmQH/A5UB/wORAf8DjQH/A4gB/wOEAf8DQgH/Az0B/wM5Af8DKQH/iAADJQE2\r\n        A1wBxgHqAbwBgQH/AegBtwE6Af8B5gGyATAB/wHkAbABKwH/A14B8ANgAeMDXQHPA10BzANZAbsDVAGo\r\n        AzABSwMEAQVAAAFCAU0BPgcAAT4DAAEoAwABQAMAASADAAEBAQABAQYAAQEWAAP/AQAG/wIAAYABAQGA\r\n        AQEEAAGAAQEBgAEBBAABgAEBAYABAQQAAYABAQGAAQEEAAGAAQEBgAEBBAABgAEBAYABAQQAAYABAQGA\r\n        AQEEAAGAAQEBgAEBBAABgAEBAYABAQQAAYABAQGAAQEEAAGAAQEBgAEBBAABgAEBAYABAQQAAYABAQGA\r\n        AQEEAAGAAQEBgAEBBAAG/wIAAv8BxwHjAQABAwT/AcABAwEAAQMBgwH/AfwB/wHAAQMBAAEDAQEBgwHw\r\n        AfkBwAEDAQABAwEAAQEBAAHxAcABAwEAAQMBgAIAAeABwAEDAYABAwHAAYABAALAAQMBgAEDAfgBwAEA\r\n        AYQBwAEDAYABAQL4AQABhAHAAQMBwAEBAvgBAALAAQMBwAEBAvgBAAHgAcABAwHAAQAC+AEAAfEBwAED\r\n        AcABAAL4AfAB+QHAAQMBwAEAAfgBAAH8Af8BwAEDAcABAAH4AQAC/wHHAeMBwAEAAfgBAAT/AcABAAL/\r\n        Cw==\r\n</value>\r\n  </data>\r\n</root>"
  },
  {
    "path": "SidWiz/app.config",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<configuration>\r\n  <runtime>\r\n    <gcAllowVeryLargeObjects enabled=\"true\"/>\r\n    <!-- AppContextSwitchOverrides value attribute is in the form of 'key1=true|false;key2=true|false  -->\r\n    <AppContextSwitchOverrides value=\"Switch.UseLegacyAccessibilityFeatures=false\"/>\r\n  </runtime>\r\n  <startup>\r\n    <supportedRuntime version=\"v4.0\" sku=\".NETFramework,Version=v4.8\"/>\r\n  </startup>\r\n</configuration>\r\n"
  },
  {
    "path": "SidWiz.sln",
    "content": "Microsoft Visual Studio Solution File, Format Version 12.00\r\n# Visual Studio Version 17\r\nVisualStudioVersion = 17.10.35004.147\r\nMinimumVisualStudioVersion = 10.0.40219.1\r\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"SidWizPlusGUI\", \"SidWiz\\SidWizPlusGUI.csproj\", \"{190749A3-C233-4825-9678-66CA9CEF295E}\"\r\nEndProject\r\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"LibSidWiz\", \"LibSidWiz\\LibSidWiz.csproj\", \"{75DD5F75-A354-4348-8A2F-7EE0F6AD6D18}\"\r\nEndProject\r\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"SidWizPlus\", \"SidWizPlus\\SidWizPlus.csproj\", \"{6E0D8279-E2FA-4FF5-921F-A408915A93A0}\"\r\nEndProject\r\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"LibVgm\", \"LibVgm\\LibVgm.csproj\", \"{50DBEB28-7D52-4658-A276-724E7415B7E1}\"\r\nEndProject\r\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Solution Items\", \"Solution Items\", \"{E67AB1BB-89A3-43C3-BB08-C3E10462D84D}\"\r\n\tProjectSection(SolutionItems) = preProject\r\n\t\t.editorconfig = .editorconfig\r\n\t\tDirectory.Build.props = Directory.Build.props\r\n\tEndProjectSection\r\nEndProject\r\nGlobal\r\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\r\n\t\tDebug|Any CPU = Debug|Any CPU\r\n\t\tRelease|Any CPU = Release|Any CPU\r\n\tEndGlobalSection\r\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\r\n\t\t{190749A3-C233-4825-9678-66CA9CEF295E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\r\n\t\t{190749A3-C233-4825-9678-66CA9CEF295E}.Debug|Any CPU.Build.0 = Debug|Any CPU\r\n\t\t{190749A3-C233-4825-9678-66CA9CEF295E}.Release|Any CPU.ActiveCfg = Release|Any CPU\r\n\t\t{190749A3-C233-4825-9678-66CA9CEF295E}.Release|Any CPU.Build.0 = Release|Any CPU\r\n\t\t{75DD5F75-A354-4348-8A2F-7EE0F6AD6D18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\r\n\t\t{75DD5F75-A354-4348-8A2F-7EE0F6AD6D18}.Debug|Any CPU.Build.0 = Debug|Any CPU\r\n\t\t{75DD5F75-A354-4348-8A2F-7EE0F6AD6D18}.Release|Any CPU.ActiveCfg = Release|Any CPU\r\n\t\t{75DD5F75-A354-4348-8A2F-7EE0F6AD6D18}.Release|Any CPU.Build.0 = Release|Any CPU\r\n\t\t{6E0D8279-E2FA-4FF5-921F-A408915A93A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\r\n\t\t{6E0D8279-E2FA-4FF5-921F-A408915A93A0}.Debug|Any CPU.Build.0 = Debug|Any CPU\r\n\t\t{6E0D8279-E2FA-4FF5-921F-A408915A93A0}.Release|Any CPU.ActiveCfg = Release|Any CPU\r\n\t\t{6E0D8279-E2FA-4FF5-921F-A408915A93A0}.Release|Any CPU.Build.0 = Release|Any CPU\r\n\t\t{50DBEB28-7D52-4658-A276-724E7415B7E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\r\n\t\t{50DBEB28-7D52-4658-A276-724E7415B7E1}.Debug|Any CPU.Build.0 = Debug|Any CPU\r\n\t\t{50DBEB28-7D52-4658-A276-724E7415B7E1}.Release|Any CPU.ActiveCfg = Release|Any CPU\r\n\t\t{50DBEB28-7D52-4658-A276-724E7415B7E1}.Release|Any CPU.Build.0 = Release|Any CPU\r\n\tEndGlobalSection\r\n\tGlobalSection(SolutionProperties) = preSolution\r\n\t\tHideSolutionNode = FALSE\r\n\tEndGlobalSection\r\n\tGlobalSection(ExtensibilityGlobals) = postSolution\r\n\t\tSolutionGuid = {3609C250-9081-41B1-B36F-4DE0AA0E2F38}\r\n\tEndGlobalSection\r\nEndGlobal\r\n"
  },
  {
    "path": "SidWiz.sln.DotSettings",
    "content": "﻿<wpf:ResourceDictionary xml:space=\"preserve\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:s=\"clr-namespace:System;assembly=mscorlib\" xmlns:ss=\"urn:shemas-jetbrains-com:settings-storage-xaml\" xmlns:wpf=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=Autogenerate/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=endian/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=FFMPEG/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=Frameskip/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=Multidumper/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=Okim/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=playlists/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=Scsp/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=sidplayfp/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=sidwizplus/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=Spcm/@EntryIndexedValue\">True</s:Boolean>\n\t\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=trackbar/@EntryIndexedValue\">True</s:Boolean></wpf:ResourceDictionary>"
  },
  {
    "path": "SidWizPlus/App.config",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<configuration>\r\n    <startup> \r\n        <supportedRuntime version=\"v4.0\" sku=\".NETFramework,Version=v4.8\"/>\r\n    </startup>\r\n</configuration>\r\n"
  },
  {
    "path": "SidWizPlus/BackgroundRenderer.cs",
    "content": "﻿using System;\r\nusing System.Drawing;\r\nusing System.Drawing.Drawing2D;\r\nusing System.Drawing.Imaging;\r\nusing System.Drawing.Text;\r\nusing System.Windows.Forms;\r\n\r\nnamespace SidWizPlus\r\n{\r\n    class BackgroundRenderer: IDisposable\r\n    {\r\n        public Image Image { get; }\r\n        public Rectangle WaveArea { get; private set; }\r\n\r\n        private readonly Graphics _graphics;\r\n        private readonly double _aspectRatio;\r\n        private readonly int _width;\r\n        private readonly int _height;\r\n\r\n        public BackgroundRenderer(int width, int height, Color backgroundColor)\r\n        {\r\n            _width = width;\r\n            _height = height;\r\n            _aspectRatio = _width / (double) _height;\r\n\r\n            Image = new Bitmap(_width, _height, PixelFormat.Format32bppArgb);\r\n            WaveArea = new Rectangle(0, 0, _width, _height);\r\n            _graphics = Graphics.FromImage(Image);\r\n            // Fill it in\r\n            using (var brush = new SolidBrush(backgroundColor))\r\n            {\r\n                _graphics.FillRectangle(brush, WaveArea);\r\n            }\r\n            _graphics.TextRenderingHint = TextRenderingHint.AntiAlias;\r\n        }\r\n\r\n        public void Dispose()\r\n        {\r\n            _graphics?.Dispose();\r\n            Image?.Dispose();\r\n        }\r\n\r\n\r\n        public void Add(ImageInfo imageInfo)\r\n        {\r\n            int imageWidth = imageInfo.Image.Width;\r\n            int imageHeight = imageInfo.Image.Height;\r\n            // Compute size if stretching\r\n            if (imageInfo.StretchToFit)\r\n            {\r\n                double imageAspectRatio = imageWidth / (double) imageHeight;\r\n                if (imageAspectRatio > _aspectRatio)\r\n                {\r\n                    // Image has a wider aspect ratio\r\n                    imageWidth = _width;\r\n                    imageHeight = (int) Math.Round(_width / imageAspectRatio);\r\n                }\r\n                else\r\n                {\r\n                    imageHeight = _height;\r\n                    imageWidth = (int) Math.Round(_height * imageAspectRatio);\r\n                }\r\n            }\r\n\r\n            // Compute where to draw it\r\n            var rect = AlignedRect(imageInfo.Alignment, _width, _height, imageWidth, imageHeight);\r\n            var ia = new ImageAttributes();\r\n            if (imageInfo.StretchToFit)\r\n            {\r\n                // Make sure stretched images don't have fuzzy edges\r\n                ia.SetWrapMode(WrapMode.TileFlipXY);\r\n            }\r\n            // Apply any alpha\r\n            if (imageInfo.Alpha < 1.0)\r\n            {\r\n                ia.SetColorMatrix(new ColorMatrix {Matrix33 = imageInfo.Alpha});\r\n            }\r\n            _graphics.DrawImage(imageInfo.Image, rect, 0, 0, imageInfo.Image.Width, imageInfo.Image.Height, GraphicsUnit.Pixel, ia);\r\n            // Apply constriction\r\n            Constrain(rect, imageInfo.ConstrainWaves);\r\n        }\r\n\r\n        public void Add(TextInfo textInfo)\r\n        {\r\n            using var font = new Font(textInfo.FontName, textInfo.FontSize, textInfo.FontStyle);\r\n            // Measure it\r\n            var size = _graphics.MeasureString(textInfo.Text, font, new SizeF(_width, _height));\r\n            // Draw it\r\n            var rect = AlignedRect(textInfo.Alignment, _width, _height, (int) Math.Ceiling(size.Width),\r\n                (int) Math.Ceiling(size.Height));\r\n            using (var brush = new SolidBrush(textInfo.Color))\r\n            {\r\n                _graphics.DrawString(textInfo.Text, font, brush, rect, new StringFormat\r\n                {\r\n                    Alignment = textInfo.Alignment switch\r\n                    {\r\n                        ContentAlignment.BottomCenter or ContentAlignment.MiddleCenter or ContentAlignment.TopCenter => StringAlignment.Center,\r\n                        ContentAlignment.BottomLeft or ContentAlignment.MiddleLeft or ContentAlignment.TopLeft => StringAlignment.Near,\r\n                        _ => StringAlignment.Far,\r\n                    }\r\n                });\r\n            }\r\n\r\n            // Apply constriction\r\n            Constrain(rect, textInfo.ConstrainWaves);\r\n        }\r\n\r\n        private Rectangle AlignedRect(ContentAlignment alignment, int width, int height, int imageWidth, int imageHeight)\r\n        {\r\n            return alignment switch\r\n            {\r\n                ContentAlignment.TopLeft => new Rectangle(0, 0, imageWidth, imageHeight),\r\n                ContentAlignment.TopCenter => new Rectangle((width - imageWidth) / 2, 0, imageWidth, imageHeight),\r\n                ContentAlignment.TopRight => new Rectangle(width - imageWidth, 0, imageWidth, imageHeight),\r\n                ContentAlignment.MiddleLeft => new Rectangle(0, (height - imageHeight) / 2, imageWidth, imageHeight),\r\n                ContentAlignment.MiddleCenter => new Rectangle((width - imageWidth) / 2, (height - imageHeight) / 2, imageWidth, imageHeight),\r\n                ContentAlignment.MiddleRight => new Rectangle(width - imageWidth, (height - imageHeight) / 2, imageWidth, imageHeight),\r\n                ContentAlignment.BottomLeft => new Rectangle(0, height - imageHeight, imageWidth, imageHeight),\r\n                ContentAlignment.BottomCenter => new Rectangle((width - imageWidth) / 2, height - imageHeight, imageWidth, imageHeight),\r\n                ContentAlignment.BottomRight => new Rectangle(width - imageWidth, height - imageHeight, imageWidth, imageHeight),\r\n                _ => throw new Exception(\"Unhandled enum value \" + alignment),\r\n            };\r\n        }\r\n\r\n        private void Constrain(Rectangle source, DockStyle dockStyle)\r\n        {\r\n            var result = WaveArea;\r\n            switch (dockStyle)\r\n            {\r\n                case DockStyle.Top:\r\n                    result.Y += source.Height;\r\n                    result.Height -= source.Height;\r\n                    break;\r\n                case DockStyle.Bottom:\r\n                    result.Height -= source.Height;\r\n                    break;\r\n                case DockStyle.Left:\r\n                    result.X += source.Width;\r\n                    result.Width -= source.Width;\r\n                    break;\r\n                case DockStyle.Right:\r\n                    result.Width -= source.Width;\r\n                    break;\r\n            }\r\n\r\n            WaveArea = result;\r\n        }\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "SidWizPlus/ImageInfo.cs",
    "content": "﻿using System.Drawing;\r\nusing System.Windows.Forms;\r\n\r\nnamespace SidWizPlus\r\n{\r\n    internal class ImageInfo\r\n    {\r\n        public Image Image { get; }\r\n        public ContentAlignment Alignment { get; }\r\n        public bool StretchToFit { get; }\r\n        public DockStyle ConstrainWaves { get; }\r\n        public float Alpha { get; }\r\n\r\n        public ImageInfo(Image image, ContentAlignment alignment, bool stretchToFit, DockStyle constrainWaves, float alpha)\r\n        {\r\n            Image = image;\r\n            Alignment = alignment;\r\n            StretchToFit = stretchToFit;\r\n            ConstrainWaves = constrainWaves;\r\n            Alpha = alpha;\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "SidWizPlus/Program.cs",
    "content": "﻿using System;\r\nusing System.Collections.Generic;\r\nusing System.Diagnostics;\r\nusing System.Drawing;\r\nusing System.IO;\r\nusing System.Linq;\r\nusing System.Net;\r\nusing System.Reflection;\r\nusing System.Text;\r\nusing System.Text.RegularExpressions;\r\nusing System.Threading;\r\nusing System.Threading.Tasks;\r\nusing System.Windows.Forms;\r\nusing CommandLine;\r\nusing Google;\r\nusing Google.Apis.Auth.OAuth2;\r\nusing Google.Apis.Auth.OAuth2.Responses;\r\nusing Google.Apis.Services;\r\nusing Google.Apis.Upload;\r\nusing Google.Apis.YouTube.v3;\r\nusing Google.Apis.YouTube.v3.Data;\r\nusing LibSidWiz;\r\nusing LibSidWiz.Outputs;\r\nusing LibSidWiz.Triggers;\r\nusing LibVgm;\r\nusing Channel = LibSidWiz.Channel;\r\n\r\nnamespace SidWizPlus\r\n{\r\n    // ReSharper disable once ClassNeverInstantiated.Global\r\n    internal class Program\r\n    {\r\n        // ReSharper disable once ClassNeverInstantiated.Local\r\n        private class Settings\r\n        {\r\n            // ReSharper disable UnusedAutoPropertyAccessor.Local\r\n\r\n            [Option('f', \"files\", Separator = ',', HelpText = \"Input WAV files, comma-separated. Wildcards are accepted.\", Group = \"Inputs\")] \r\n            public IEnumerable<string> InputFiles { get; set; }\r\n\r\n            [Option('v', \"vgm\", Required = false, HelpText = \"VGM file, if specified GD3 text is drawn\", Group = \"Inputs\")]\r\n            public string VgmFile { get; set; }\r\n\r\n            [Option('m', \"master\", Required = false, HelpText = \"Master audio file, if not specified then the inputs will be mixed to a new file\")]\r\n            public string MasterAudioFile { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"nomastermix\", HelpText = \"Disable automatic generation of master audio file (on by default)\")]\r\n            public bool NoMasterMix { get; set;}\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"nomastermixreplaygain\", HelpText = \"Disable automatic ReplayGain adjustment of automatically generated master audio file (on by default)\")]\r\n            public bool NoMasterMixReplayGain { get; set;}\r\n\r\n            [Option('o', \"output\", Required = false, HelpText = \"Output file\", Group = \"Outputs\")]\r\n            public string OutputFile { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option('p', \"previewframeskip\", Required = false, HelpText = \"Enable a preview window with the specified frameskip - higher values give faster rendering by not drawing every frame to the screen.\", Group = \"Outputs\")]\r\n            public int PreviewFrameskip { get; set; }\r\n\r\n            [Option('w', \"width\", Required = false, HelpText = \"Width of image rendered\", Default = 720*16/9)]\r\n            public int Width { get; set; }\r\n\r\n            [Option('h', \"height\", Required = false, HelpText = \"Height of image rendered\", Default = 720)]\r\n            public int Height { get; set; }\r\n\r\n            [Option('c', \"columns\", Required = false, HelpText = \"Number of columns to render\", Default = 1)]\r\n            public int Columns { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"maxaspectratio\", Required = false, HelpText = \"Maximum aspect ratio, used to automatically determine the number of columns\", Default = -1.0)]\r\n            public double MaximumAspectRatio { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"viewms\", Required = false, HelpText = \"Rendered view width in ms\", Default = 35)]\r\n            public int ViewWidthMs { get; set; }\r\n\r\n            [Option('r', \"fps\", Required = false, HelpText = \"Frame rate\", Default = 60)]\r\n            public int FramesPerSecond { get; set; }\r\n            \r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"linewidth\", Required = false, HelpText = \"Line width\", Default = 3)]\r\n            public float LineWidth { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"linecolor\", Required = false, HelpText = \"Line color, can be hex or a .net color name\", Default = \"white\")]\r\n            public string LineColor { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"fillcolor\", Required = false, HelpText = \"Fill color, can be hex or a .net color name\", Default = \"transparent\")]\r\n            public string FillColor { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"fillbase\", Required = false, HelpText = \"Fill baseline, values in range -1..+1 make sense\", Default = 0.0)]\r\n            public double FillBase { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option('a', \"autoscale\", Required = false, HelpText = \"Automatic scaling percentage. A value of 100 will make the peak amplitude just fit in the rendered area.\")]\r\n            public float AutoScalePercentage { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"autoscaleignorepercussion\", Required = false, HelpText = \"Makes automatic scaling ignore YM2413 percussion channels\")]\r\n            public bool AutoScaleIgnoreYm2413Percussion { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"labelsfromvgm\", Required = false, HelpText = \"Attempt to label channels based on their filename\")]\r\n            public bool ChannelLabelsFromVgm { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option('t', \"triggeralgorithm\", Required = false, HelpText = \"Trigger algorithm name\", Default = nameof(PeakSpeedTrigger))]\r\n            public string TriggerAlgorithm { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"triggerlookahead\", Required = false, HelpText = \"Number of frames to allow the trigger to look ahead, zero means no lookahead\", Default = 0)]\r\n            public int TriggerLookahead { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"triggerlookaheadonfailure\", Required = false, HelpText = \"Number of frames to allow the trigger to look ahead when failing to find a match with the default\", Default = 1)]\r\n            public int TriggerLookaheadOnFailureFrames { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"highpass\", Required = false, HelpText = \"Enable high-pass filtering\", Default = false)]\r\n            public bool HighPass { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"ffmpeg\", Required = false, HelpText = \"Path to FFMPEG. Required if rendering to a file. Will be discovered if on the path.\")]\r\n            public string FfMpegPath { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"vcodec\", Required = false, HelpText = \"Video codec for FFMPEG\", Default = \"libx264\")]\r\n            public string VideoCodec { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"acodec\", Required = false, HelpText = \"Audio codec for FFMPEG\", Default = \"aac\")]\r\n            public string AudioCodec { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"ffmpegoptions\", Required = false, \r\n                HelpText = \"Extra commandline options for FFMPEG, e.g. to set the output format. Surround value with quotes and start with a space, e.g. \\\" -crf 20\\\", to avoid conflicts with other parameters.\", Default = \"\")]\r\n            public string FfMpegExtraOptions { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"multidumper\", Required = false, HelpText = \"Path to MultiDumper, if specified with --vgm and no --files then it will be invoked for the VGM\")]\r\n            // ReSharper disable once IdentifierTypo\r\n            public string MultidumperPath { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"multidumpersamplingrate\", Required = false, HelpText = \"Sampling rate for MultiDumper\", Default = 44100)]\r\n            // ReSharper disable once IdentifierTypo\r\n            public int MultidumperSamplingRate { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"multidumperloopcount\", Required = false, HelpText = \"Loop count for MultiDumper\", Default = 2)]\r\n            // ReSharper disable once IdentifierTypo\r\n            public int MultidumperLoopCount { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"multidumperfadeoutms\", Required = false, HelpText = \"Fade out time after looping for MultiDumper, in ms\", Default = 8000)]\r\n            // ReSharper disable once IdentifierTypo\r\n            public int MultidumperFadeOutMs { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"multidumpergapms\", Required = false, HelpText = \"Gap time for non-looping tracks for MultiDumper, in ms\", Default = 1000)]\r\n            // ReSharper disable once IdentifierTypo\r\n            public int MultidumperGapMs { get; set; }\r\n            \r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"multidumperoptions\", Required = false, HelpText = \"Extra arguments for MultiDumper. Surround value with quotes and start with a space, e.g. \\\" --default_filter\\\", to avoid conflicts with other parameters.\", Default = \"\")]\r\n            // ReSharper disable once IdentifierTypo\r\n            public string MultidumperOptions { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"silencethreshold\", Required = false, HelpText = \"Amplitude range treated as silent auto-importing from MultiDumper\", Default = 0.01f)]\r\n            // ReSharper disable once IdentifierTypo\r\n            public float SilenceThreshold { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"backgroundcolor\", Required = false, HelpText = \"Background color, can be hex or a .net color name\", Default = \"black\")]\r\n            public string BackgroundColor { get; set; }\r\n\r\n            [Option(\"background\", Required = false, HelpText = \"Background image, drawn transparently in the background\")]\r\n            public string BackgroundImageFile { get; set; }\r\n            \r\n            [Option(\"logo\", Required = false, HelpText = \"Logo image, drawn in the lower right\")]\r\n            public string LogoImageFile { get; set; }\r\n            \r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"gridcolor\", Required = false, HelpText = \"Grid color, can be hex or a .net color name\", Default = \"white\")]\r\n            public string GridColor { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"gridwidth\", Required = false, HelpText = \"Grid line width\", Default = 0)]\r\n            public float GridLineWidth { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"gridborder\", Required = false, HelpText = \"Draw a border around the waves as well as between them\", Default = true)]\r\n            public bool GridBorder { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"zerolinecolor\", Required = false, HelpText = \"Zero line color\", Default = \"white\")]\r\n            public string ZeroLineColor { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"zerolinewith\", Required = false, HelpText = \"Zero line width\", Default = 0)]\r\n            public float ZeroLineWidth { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"gd3font\", Required = false, HelpText = \"Font for GD3 info\", Default = \"Tahoma\")]\r\n            public string Gd3Font { get; set; }\r\n            [Option(\"gd3size\", Required = false, HelpText = \"Font size (in points) for GD3 info\", Default = 16)]\r\n            public float Gd3FontSize { get; set; }\r\n            [Option(\"gd3color\", Required = false, HelpText = \"Font color for GD3 info\", Default = \"white\")]\r\n            public string Gd3FontColor { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"labelsfont\", Required = false, HelpText = \"Font for channel labels\")]\r\n            public string ChannelLabelsFont { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"labelssize\", HelpText = \"Font size for channel labels\", Default = 8)]\r\n            public float ChannelLabelsSize { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"labelscolor\", HelpText = \"Font color for channel labels\", Default = \"white\")]\r\n            public string ChannelLabelsColor { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"labelspadding\", HelpText = \"Padding for channel labels - more specific settings override this\", Default = 0)]\r\n            public int ChannelLabelsPadding { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"labelspaddingleft\", HelpText = \"Left padding for channel labels\")]\r\n            public int? ChannelLabelsPaddingLeft { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"labelspaddingright\", HelpText = \"Right padding for channel labels\")]\r\n            public int? ChannelLabelsPaddingRight { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"labelspaddingtop\", HelpText = \"Top padding for channel labels\")]\r\n            public int? ChannelLabelsPaddingTop { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"labelspaddingbottom\", HelpText = \"Bottom padding for channel labels\")]\r\n            public int? ChannelLabelsPaddingBottom { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"labelsalignment\", HelpText = \"Alignment for channel labels\", Default = ContentAlignment.TopLeft)]\r\n            public ContentAlignment ChannelLabelsAlignment { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"youtubesecret\", HelpText = \"YouTube client secret JSON file. Use this to specify a custom OAth key if the embedded one doesn't work.\")]\r\n            public string YouTubeUploadClientSecret { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"youtubetitle\", HelpText = \"YouTube video title. If a VGM is specified then you can reference GD3 tags like [title], [system], [game], [composer]\")]\r\n            public string YouTubeTitle { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"youtubecategory\", HelpText = \"YouTube video category\", Default = \"Gaming\")]\r\n            public string YouTubeCategory { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"youtubetags\", HelpText = \"YouTube video tags, comma separated\")]\r\n            public string YouTubeTags { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"youtubetagsfromgd3\", HelpText = \"Populate additional tags from the GD3 tag\")]\r\n            public bool YouTubeTagsFromGd3 { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"youtubeplaylist\", HelpText = \"YouTube playlist title. If a VGM is specified then you can reference GD3 tags like [title], [system], [game], [composer]\")]\r\n            public string YouTubePlaylist { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"youtubeplaylistdescriptionfile\", HelpText = \"Use the specified file for the playlist description\")]\r\n            public string YouTubePlaylistDescriptionFile { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"youtubedescriptionsextratext\", HelpText = \"Extra text to append to descriptions\")]\r\n            public string YouTubeDescriptionsExtra { get; set; }\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"youtubeonly\", HelpText = \"Only upload to YouTube\")]\r\n            public bool YouTubeOnly { get; set; }\r\n\r\n            // ReSharper disable once StringLiteralTypo\r\n            [Option(\"youtubemerge\", HelpText = \"Merge the specified videos (wildcard, results sorted alphabetically) to one file and upload to YouTube\", Group=\"Inputs\")]\r\n            public string YouTubeMerge { get; set; }\r\n\r\n            [Option(\"threads\", HelpText = \"Number of rendering threads to use. Defaults to as many CPUs as your computer has.\", Default = -1)]\r\n            public int ThreadCount { get; set; }\r\n\r\n            [Option(\"verbose\", HelpText = \"Enable even more logging\", Default = false)]\r\n            public bool Verbose { get; set; }\r\n        }\r\n\r\n        static int Main(string[] args)\r\n        {\r\n            try\r\n            {\r\n                Console.OutputEncoding = Encoding.UTF8;\r\n\r\n                // ReSharper disable once RedundantNameQualifier\r\n                new CommandLine.Parser(settings =>\r\n                {\r\n                    settings.CaseSensitive = false;\r\n                    settings.AutoHelp = true;\r\n                    settings.AutoVersion = true;\r\n                    settings.HelpWriter = Console.Error;\r\n                })\r\n                    .ParseArguments<Settings>(args)\r\n                    .WithParsed(Run);\r\n            }\r\n            catch (Exception e)\r\n            {\r\n                Console.Error.WriteLine($\"Fatal: {e}\");\r\n                return 1;\r\n            }\r\n\r\n            return 0;\r\n        }\r\n\r\n        private static void Run(Settings settings)\r\n        {\r\n            if (!settings.YouTubeOnly)\r\n            {\r\n                if (settings.VgmFile != null)\r\n                {\r\n                    LogVerbose(settings, \"Running MultiDumper because VGM file was specified\");\r\n                    RunMultiDumper(settings);\r\n                }\r\n                else\r\n                {\r\n                    // We want to expand any wildcards in the input file list (and also fully qualify them)\r\n                    LogVerbose(settings, \"Expanding wildcards in inputs...\");\r\n                    var inputs = new List<string>();\r\n                    foreach (var inputFile in settings.InputFiles)\r\n                    {\r\n                        if (File.Exists(inputFile))\r\n                        {\r\n                            inputs.Add(Path.GetFullPath(inputFile));\r\n                            continue;\r\n                        }\r\n\r\n                        var pattern = Path.GetFileName(inputFile) ??\r\n                            throw new Exception($\"Failed to match {inputFile}\");\r\n                        var pathPart = inputFile.Substring(0, inputFile.Length - pattern.Length);\r\n                        var directory = pathPart.Length > 0\r\n                            ? Path.GetFullPath(pathPart)\r\n                            : Directory.GetCurrentDirectory();\r\n                        var files = Directory.EnumerateFiles(directory, pattern).ToList();\r\n                        if (files.Count == 0)\r\n                        {\r\n                            throw new Exception($\"Failed to match {inputFile}\");\r\n                        }\r\n\r\n                        inputs.AddRange(files);\r\n                    }\r\n\r\n                    settings.InputFiles = inputs;\r\n                    LogVerbose(settings, string.Join(\"\\n- \", Enumerable.Repeat(\"Input files are:\", 1).Concat(inputs)));\r\n                }\r\n\r\n                if (settings.InputFiles == null || !settings.InputFiles.Any())\r\n                {\r\n                    throw new Exception(\"No inputs specified\");\r\n                }\r\n\r\n                LogVerbose(settings, \"Creating channel objects...\");\r\n                var channels = settings.InputFiles\r\n                    .AsParallel()\r\n                    .Select(filename =>\r\n                    {\r\n                        var channel = new Channel(false)\r\n                        {\r\n                            Filename = filename,\r\n                            LineColor = ParseColor(settings.LineColor),\r\n                            LineWidth = settings.LineWidth,\r\n                            FillColor = ParseColor(settings.FillColor),\r\n                            FillBase = settings.FillBase,\r\n                            Label = Channel.GuessNameFromMultidumperFilename(filename),\r\n                            Algorithm = CreateTriggerAlgorithm(settings.TriggerAlgorithm),\r\n                            TriggerLookaheadFrames = settings.TriggerLookahead,\r\n                            TriggerLookaheadOnFailureFrames = settings.TriggerLookaheadOnFailureFrames,\r\n                            SilenceThreshold = settings.SilenceThreshold,\r\n                            ZeroLineWidth = settings.ZeroLineWidth,\r\n                            ZeroLineColor = ParseColor(settings.ZeroLineColor),\r\n                            LabelFont = settings.ChannelLabelsFont == null\r\n                                ? null\r\n                                : new Font(settings.ChannelLabelsFont, settings.ChannelLabelsSize),\r\n                            LabelColor = ParseColor(settings.ChannelLabelsColor),\r\n                            LabelAlignment = settings.ChannelLabelsAlignment,\r\n                            LabelMargins = new Padding(\r\n                                settings.ChannelLabelsPaddingLeft ?? settings.ChannelLabelsPadding,\r\n                                settings.ChannelLabelsPaddingTop ?? settings.ChannelLabelsPadding,\r\n                                settings.ChannelLabelsPaddingRight ?? settings.ChannelLabelsPadding,\r\n                                settings.ChannelLabelsPaddingBottom ?? settings.ChannelLabelsPadding),\r\n                            HighPassFilter = settings.HighPass\r\n                        };\r\n                        channel.LoadDataAsync().Wait();\r\n                        // We can only set this when the file is loaded\r\n                        channel.ViewWidthInMilliseconds = settings.ViewWidthMs;\r\n                        return channel;\r\n                    })\r\n                    .Where(ch => ch.SampleCount > 0 && !ch.IsSilent)\r\n                    .OrderByAlphaNumeric(ch => ch.Filename)\r\n                    .ToList();\r\n\r\n                if (settings.AutoScalePercentage > 0)\r\n                {\r\n                    LogVerbose(settings, \"Auto-scaling...\");\r\n\r\n                    float max;\r\n\r\n                    static bool IsYm2413Percussion(Channel ch) =>\r\n                        ch.Label.StartsWith(\"YM2413 \") && !ch.Label.StartsWith(\"YM2413 Tone\");\r\n\r\n                    if (settings.AutoScaleIgnoreYm2413Percussion)\r\n                    {\r\n                        var channelsToUse = channels.Where(channel => !IsYm2413Percussion(channel)).ToList();\r\n                        if (channelsToUse.Count == 0)\r\n                        {\r\n                            // Fall back on overall max if all channels are percussion\r\n                            channelsToUse = channels;\r\n                        }\r\n\r\n                        max = channelsToUse.Max(ch => ch.Max);\r\n                    }\r\n                    else\r\n                    {\r\n                        max = channels.Max(ch => ch.Max);\r\n                    }\r\n\r\n                    var scale = settings.AutoScalePercentage / 100 / max;\r\n                    LogVerbose(settings, $\"Applying scale of {scale} to all channels\");\r\n                    foreach (var channel in channels)\r\n                    {\r\n                        channel.Scale = scale;\r\n                    }\r\n                }\r\n\r\n                if (settings.ChannelLabelsFromVgm && settings.VgmFile != null)\r\n                {\r\n                    LogVerbose(settings, \"Guessing channel labels from VGM...\");\r\n                    TryGuessLabelsFromVgm(channels, settings.VgmFile);\r\n                }\r\n\r\n                if (settings.OutputFile != null)\r\n                {\r\n                    // Emit normalized data to a WAV file for later mixing\r\n                    if (settings.MasterAudioFile == null && !settings.NoMasterMix)\r\n                    {\r\n                        settings.MasterAudioFile = settings.OutputFile + \".wav\";\r\n                        LogVerbose(settings, \"Generating master audio file...\");\r\n                        Mixer.MixToFile(channels, settings.MasterAudioFile, !settings.NoMasterMixReplayGain);\r\n                    }\r\n                }\r\n\r\n                LogVerbose(settings, \"Starting render...\");\r\n                Render(settings, channels);\r\n                LogVerbose(settings, \"Render complete\");\r\n\r\n                foreach (var channel in channels)\r\n                {\r\n                    channel.Dispose();\r\n                }\r\n            }\r\n\r\n            if (settings.YouTubeTitle != null)\r\n            {\r\n                if (settings.YouTubeMerge != null)\r\n                {\r\n                    UploadMergedToYouTube(settings).Wait();\r\n                }\r\n                else\r\n                {\r\n                    UploadToYouTube(settings).Wait();\r\n                }\r\n            }\r\n        }\r\n\r\n        private class InstrumentState\r\n        {\r\n            public int Instrument { private get; set; }\r\n            public int Ticks { get; private set; }\r\n\r\n            private static readonly string[] Names =\r\n            [\r\n                \"Custom Instrument\",\r\n                \"Violin\",\r\n                \"Guitar\",\r\n                \"Piano\",\r\n                \"Flute\",\r\n                \"Clarinet\",\r\n                \"Oboe\",\r\n                \"Trumpet\",\r\n                \"Organ\",\r\n                \"Horn\",\r\n                \"Synthesizer\",\r\n                \"Harpsichord\",\r\n                \"Vibraphone\",\r\n                \"Synthesizer Bass\",\r\n                \"Acoustic Bass\",\r\n                \"Electric Guitar\"\r\n            ];\r\n\r\n            public string Name => Names[Instrument];\r\n\r\n            public override string ToString() => $\"{Name} ({TimeSpan.FromSeconds((double)Ticks / 44100)})\";\r\n\r\n            public void AddTime(int ticks)\r\n            {\r\n                Ticks += ticks;\r\n            }\r\n        }\r\n\r\n        private class ChannelState\r\n        {\r\n            private readonly List<InstrumentState> _instruments = [];\r\n            private readonly Dictionary<int, InstrumentState> _instrumentsByChannel = [];\r\n            private InstrumentState _currentInstrument;\r\n            public bool KeyDown { private get; set; }\r\n\r\n            public void SetInstrument(int instrument)\r\n            {\r\n                if (!_instrumentsByChannel.TryGetValue(instrument, out var state))\r\n                {\r\n                    state = new InstrumentState {Instrument = instrument};\r\n                    _instruments.Add(state);\r\n                    _instrumentsByChannel.Add(instrument, state);\r\n                }\r\n\r\n                _currentInstrument = state;\r\n            }\r\n\r\n            public void AddTime(int ticks)\r\n            {\r\n                if (KeyDown)\r\n                {\r\n                    _currentInstrument?.AddTime(ticks);\r\n                }\r\n            }\r\n\r\n            public IEnumerable<InstrumentState> Instruments => _instruments;\r\n\r\n            public override string ToString() => string.Join(\", \", Instruments.Where(x => x.Ticks > 0));\r\n        }\r\n\r\n        private static void TryGuessLabelsFromVgm(List<Channel> channels, string vgmFile)\r\n        {\r\n            var file = new VgmFile(vgmFile);\r\n\r\n            var channelStates = new Dictionary<int, ChannelState>();\r\n            ChannelState GetChannelState(int channelIndex)\r\n            {\r\n                if (!channelStates.TryGetValue(channelIndex, out var channelState))\r\n                {\r\n                    channelState = new ChannelState();\r\n                    channelStates.Add(channelIndex, channelState);\r\n                }\r\n\r\n                return channelState;\r\n            }\r\n\r\n            foreach (var command in file.Commands())\r\n            {\r\n                switch (command)\r\n                {\r\n                    case VgmFile.WaitCommand waitCommand:\r\n                        // Wait\r\n                        foreach (var channelState in channelStates.Values)\r\n                        {\r\n                            channelState.AddTime(waitCommand.Ticks);\r\n                        }\r\n                        break;\r\n                    case VgmFile.AddressDataCommand c:\r\n                    {\r\n                        if (c.Address is >= 0x30 and <= 0x38)\r\n                        {\r\n                            // YM2413 instrument\r\n                            GetChannelState(c.Address & 0xf).SetInstrument(c.Data >> 4);\r\n                        }\r\n                        else if (c.Address is >= 0x20 and <= 0x28)\r\n                        {\r\n                            // YM2413 key down\r\n                            var channelState = GetChannelState(c.Address & 0xf);\r\n                            channelState.KeyDown = (c.Data & 0b00010000) != 0;\r\n                        }\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n\r\n            foreach (var kvp in channelStates.OrderBy(x => x.Key))\r\n            {\r\n                Console.WriteLine($\"YM2413 channel {kvp.Key}: {kvp.Value}\");\r\n            }\r\n\r\n            foreach (var channel in channels.Where(c => c.Label.StartsWith(\"YM2413 Tone \")))\r\n            {\r\n                var match = Regex.Match(channel.Label, \"^YM2413 Tone (?<index>[0-9])$\");\r\n                if (!match.Success)\r\n                {\r\n                    continue;\r\n                }\r\n                var index = Convert.ToInt32(match.Groups[\"index\"].Value) - 1;\r\n                if (channelStates.TryGetValue(index, out var channelState))\r\n                {\r\n                    var instruments = channelState.Instruments\r\n                        .Where(x => x.Ticks > 0)\r\n                        .Select(x => x.Name);\r\n                    channel.Label += \": \" + string.Join(\"/\\x200b\", instruments);\r\n\r\n                    Console.WriteLine($\"Channel {index} is {channel.Label}\");\r\n                }\r\n            }\r\n        }\r\n\r\n        private static Color ParseColor(string value)\r\n        {\r\n            // If it looks like hex, use that.\r\n            // We support 3, 6 or 8 hex chars.\r\n            var match = Regex.Match(value, \"^#?(?<hex>[0-9a-fA-F]{3}([0-9a-fA-F]{3})?([0-9a-fA-F]{2})?)$\");\r\n            if (match.Success)\r\n            {\r\n                var hex = match.Groups[\"hex\"].Value;\r\n                if (hex.Length == 3)\r\n                {\r\n                    hex = $\"{hex[0]}{hex[0]}{hex[1]}{hex[1]}{hex[2]}{hex[2]}\";\r\n                }\r\n\r\n                if (hex.Length == 6)\r\n                {\r\n                    hex = \"ff\" + hex;\r\n                }\r\n                int alpha = Convert.ToInt32(hex.Substring(0, 2), 16);\r\n                int red = Convert.ToInt32(hex.Substring(2, 2), 16);\r\n                int green = Convert.ToInt32(hex.Substring(4, 2), 16);\r\n                int blue = Convert.ToInt32(hex.Substring(6, 2), 16);\r\n                return Color.FromArgb(alpha, red, green, blue);\r\n            }\r\n            // Then try named colors\r\n            var property = typeof(Color)\r\n                .GetProperties(BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Public)\r\n                .FirstOrDefault(p =>\r\n                    p.PropertyType == typeof(Color) &&\r\n                    p.Name.Equals(value, StringComparison.InvariantCultureIgnoreCase));\r\n            return property == null \r\n                ? throw new Exception($\"Could not parse color {value}\") : \r\n                (Color)property.GetValue(null);\r\n        }\r\n\r\n        private static void RunMultiDumper(Settings settings)\r\n        {\r\n            if (!File.Exists(settings.MultidumperPath))\r\n            {\r\n                settings.MultidumperPath = FindExecutable(\"multidumper.exe\");\r\n            }\r\n\r\n            if (settings.MultidumperPath == null && settings.VgmFile != null)\r\n            {\r\n                throw new Exception(\"Please pass --multidumper parameter to load a VGM file\");\r\n            }\r\n\r\n            if (settings.VgmFile != null && settings.InputFiles.Any())\r\n            {\r\n                throw new Exception(\"Can't pass both --files and --vgm\");\r\n            }\r\n\r\n            if (settings.VgmFile == null)\r\n            {\r\n                throw new Exception(\"VGM file not specified\");\r\n            }\r\n\r\n            // We normalize the VGM path here because we need to know its directory...\r\n            settings.VgmFile = Path.GetFullPath(settings.VgmFile);\r\n            // Check if we have WAVs. Note that we use \"natural\" sorting to make sure 10 comes after 9.\r\n            settings.InputFiles = Directory.EnumerateFiles(\r\n                    Path.GetDirectoryName(settings.VgmFile) ?? throw new Exception($\"Can't get path from VGM \\\"{settings.VgmFile}\\\"\"),\r\n                    Path.GetFileNameWithoutExtension(settings.VgmFile) + \" - *.wav\")\r\n                .ToList();\r\n            if (!settings.InputFiles.Any())\r\n            {\r\n                Console.WriteLine(\"Running MultiDumper...\");\r\n                // Let's run it\r\n                var wrapper = new MultiDumperWrapper(\r\n                    settings.MultidumperPath, \r\n                    settings.MultidumperSamplingRate, \r\n                    settings.MultidumperLoopCount, \r\n                    settings.MultidumperFadeOutMs,\r\n                    settings.MultidumperGapMs,\r\n                    settings.MultidumperOptions);\r\n                var song = wrapper.GetSongs(settings.VgmFile).First();\r\n                var filenames = wrapper.Dump(song, d => Console.Write($\"\\r{d:P0}\"));\r\n                settings.InputFiles = filenames.ToList();\r\n                Console.WriteLine($\" done. {settings.InputFiles.Count()} files found.\");\r\n            }\r\n            else\r\n            {\r\n                Console.WriteLine($\"Skipping MultiDumper as {settings.InputFiles.Count()} files were already present.\");\r\n            }\r\n        }\r\n\r\n        private static void LogVerbose(Settings settings, string message)\r\n        {\r\n            if (!settings.Verbose)\r\n            {\r\n                return;\r\n            }\r\n            Console.WriteLine(message);\r\n        }\r\n\r\n        private static void Render(Settings settings, List<Channel> channels)\r\n        {\r\n            Console.WriteLine(\"Generating background image...\");\r\n\r\n            var backgroundImage = new BackgroundRenderer(settings.Width, settings.Height, ParseColor(settings.BackgroundColor));\r\n            if (settings.BackgroundImageFile != null)\r\n            {\r\n                using var bm = Image.FromFile(settings.BackgroundImageFile);\r\n                backgroundImage.Add(new ImageInfo(bm, ContentAlignment.MiddleCenter, true, DockStyle.None, 0.5f));\r\n            }\r\n\r\n            if (!string.IsNullOrEmpty(settings.LogoImageFile))\r\n            {\r\n                using var bm = Image.FromFile(settings.LogoImageFile);\r\n                backgroundImage.Add(new ImageInfo(bm, ContentAlignment.BottomRight, false, DockStyle.None, 1));\r\n            }\r\n\r\n            if (settings.VgmFile != null)\r\n            {\r\n                var gd3 = Gd3Tag.LoadFromVgm(settings.VgmFile);\r\n                var gd3Text = gd3.ToString();\r\n                if (gd3Text.Length > 0)\r\n                {\r\n                    backgroundImage.Add(new TextInfo(gd3Text, settings.Gd3Font, settings.Gd3FontSize, ContentAlignment.BottomLeft, FontStyle.Regular,\r\n                        DockStyle.Bottom, ParseColor(settings.Gd3FontColor)));\r\n                }\r\n            }\r\n\r\n            if (settings.MaximumAspectRatio > 0.0)\r\n            {\r\n                Console.WriteLine($\"Determining column count for maximum aspect ratio {settings.MaximumAspectRatio}:\");\r\n                for (var columns = 1; columns < 100; ++columns)\r\n                {\r\n                    var width = backgroundImage.WaveArea.Width / columns;\r\n                    var rows = channels.Count / columns + (channels.Count % columns == 0 ? 0 : 1);\r\n                    var height = backgroundImage.WaveArea.Height / rows;\r\n                    var ratio = (double) width / height;\r\n                    Console.WriteLine($\"- {columns} columns => {width} x {height} pixels => ratio {ratio}\");\r\n                    if (ratio < settings.MaximumAspectRatio)\r\n                    {\r\n                        settings.Columns = columns;\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n\r\n            // If we are doing only YM2413, we consider adding a blank channel after the tone channels\r\n            if (channels.All(x => x.Label.Contains(\"YM2413\") && channels.Count % settings.Columns != 0))\r\n            {\r\n                var numTone = channels.Count(x => x.Label.Contains(\" Tone \"));\r\n                // We add enough to pad out the tones, or to right-fill the percussion, whichever is fewer.\r\n                var numToAdd = Math.Min(numTone % settings.Columns, channels.Count % settings.Columns);\r\n                // Add them\r\n                if (numToAdd > 0)\r\n                {\r\n                    var emptyChannel = new Channel(false)\r\n                    {\r\n                        Filename = \"\"\r\n                    };\r\n                    emptyChannel.LoadDataAsync().Wait();\r\n                    for (var i = 0; i < numToAdd; ++i)\r\n                    {\r\n                        channels.InsertRange(numTone, Enumerable.Repeat(emptyChannel, numToAdd));\r\n                    }\r\n\r\n                }\r\n            }\r\n\r\n            var renderer = new WaveformRenderer\r\n            {\r\n                BackgroundImage = backgroundImage.Image,\r\n                Columns = settings.Columns,\r\n                FramesPerSecond = settings.FramesPerSecond,\r\n                Width = settings.Width,\r\n                Height = settings.Height,\r\n                SamplingRate = channels.First().SampleRate,\r\n                RenderingBounds = backgroundImage.WaveArea\r\n            };\r\n\r\n            if (settings.GridLineWidth > 0)\r\n            {\r\n                foreach (var channel in channels)\r\n                {\r\n                    channel.BorderColor = ParseColor(settings.GridColor);\r\n                    channel.BorderWidth = settings.GridLineWidth;\r\n                    channel.BorderEdges = settings.GridBorder;\r\n                }\r\n            }\r\n\r\n            // Add the data to the renderer\r\n            foreach (var channel in channels)\r\n            {\r\n                renderer.AddChannel(channel);\r\n            }\r\n\r\n            var outputs = new List<IGraphicsOutput>();\r\n            if (settings.OutputFile != null)\r\n            {\r\n                Console.WriteLine(\"Adding FFMPEG renderer...\");\r\n                if (!File.Exists(settings.FfMpegPath))\r\n                {\r\n                    // Try to find it\r\n                    settings.FfMpegPath = FindExecutable(\"ffmpeg.exe\");\r\n                }\r\n                outputs.Add(new FfmpegOutput(settings.FfMpegPath, settings.OutputFile, settings.Width, settings.Height, settings.FramesPerSecond, settings.FfMpegExtraOptions, settings.MasterAudioFile, settings.VideoCodec, settings.AudioCodec, false));\r\n            }\r\n\r\n            if (settings.PreviewFrameskip > 0)\r\n            {\r\n                Console.WriteLine(\"Adding preview renderer...\");\r\n                outputs.Add(new PreviewOutput(settings.PreviewFrameskip, true));\r\n            }\r\n\r\n            try\r\n            {\r\n                if (settings.ThreadCount < 1)\r\n                {\r\n                    settings.ThreadCount = Environment.ProcessorCount;\r\n                    LogVerbose(settings, $\"Defaulted thread count to {settings.ThreadCount}\");\r\n                }\r\n                Console.WriteLine($\"Rendering on {settings.ThreadCount} threads...\");\r\n                var sw = Stopwatch.StartNew();\r\n                renderer.Render(outputs, settings.ThreadCount, settings.Verbose);\r\n                sw.Stop();\r\n                int numFrames = (int) (channels.Max(x => x.Length).TotalSeconds * settings.FramesPerSecond);\r\n                Console.WriteLine($\"Rendering complete in {sw.Elapsed:g}, average {numFrames / sw.Elapsed.TotalSeconds:N} fps\");\r\n            }\r\n            catch (Exception ex)\r\n            {\r\n                // Should mean it was cancelled\r\n                Console.WriteLine($\"Rendering cancelled: {ex.Message}\\n{ex}\");\r\n            }\r\n            finally\r\n            {\r\n                foreach (var graphicsOutput in outputs)\r\n                {\r\n                    graphicsOutput.Dispose();\r\n                }\r\n            }\r\n        }\r\n\r\n        private static string FindExecutable(string name)\r\n        {\r\n            string fullPath;\r\n            // Look in the current directory...\r\n            if (File.Exists(name))\r\n            {\r\n                fullPath = Path.GetFullPath(name);\r\n            }\r\n            else\r\n            {\r\n                fullPath = Environment.GetEnvironmentVariable(\"PATH\")\r\n                    ?.Split(Path.PathSeparator)\r\n                    .Select(path => Path.Combine(path, name))\r\n                    .FirstOrDefault(File.Exists)\r\n                    ?? throw new Exception($\"Could not find path to {name}\");\r\n            }\r\n\r\n            Console.WriteLine($\"Found {name} at {fullPath}\");\r\n            return fullPath;\r\n        }\r\n\r\n        private static ITriggerAlgorithm CreateTriggerAlgorithm(string name)\r\n        {\r\n            var type = AppDomain.CurrentDomain\r\n                .GetAssemblies()\r\n                .SelectMany(a => a.GetTypes())\r\n                .FirstOrDefault(t =>\r\n                    typeof(ITriggerAlgorithm).IsAssignableFrom(t) &&\r\n                    t.Name.ToLowerInvariant().Equals(name.ToLowerInvariant())) \r\n                ?? throw new Exception($\"Unknown trigger algorithm \\\"{name}\\\"\");\r\n            return Activator.CreateInstance(type) as ITriggerAlgorithm;\r\n        }\r\n\r\n        private static async Task<string> UploadToYouTube(Settings settings)\r\n        {\r\n            var youtubeService = await GetYouTubeService(settings);\r\n\r\n            var video = new Video\r\n            {\r\n                Snippet = new VideoSnippet\r\n                {\r\n                    Title = settings.YouTubeTitle,\r\n                    CategoryId = \"10\" // Music\r\n                },\r\n                Status = new VideoStatus {PrivacyStatus = \"public\"}\r\n                // or \"private\" or \"public\"\r\n            };\r\n\r\n            var tags = new List<string>();\r\n            if (settings.YouTubeTags != null)\r\n            {\r\n                tags.AddRange(settings.YouTubeTags.Split(','));\r\n            }\r\n\r\n            Gd3Tag gd3 = null;\r\n            if (settings.VgmFile != null)\r\n            {\r\n                gd3 = Gd3Tag.LoadFromVgm(settings.VgmFile);\r\n            }\r\n\r\n            if (gd3 != null)\r\n            {\r\n                video.Snippet.Description = $\"Oscilloscope View of {gd3.Title}\";\r\n                if (gd3.Game.English.Length > 0)\r\n                {\r\n                    video.Snippet.Description += $\" from the game {gd3.Game.English}\";\r\n                }\r\n                if (gd3.System.English.Length > 0)\r\n                {\r\n                    video.Snippet.Description += $\" for the {gd3.System.English}\";\r\n                }\r\n                if (gd3.Composer.English.Length > 0)\r\n                {\r\n                    video.Snippet.Description += $\", composed by {gd3.Composer}\";\r\n                }\r\n                video.Snippet.Description += \".\";\r\n                if (gd3.Ripper.Length > 0)\r\n                {\r\n                    video.Snippet.Description += $\"\\nRipped by {gd3.Ripper}\";\r\n                }\r\n                if (gd3.Notes.Length > 0)\r\n                {\r\n                    video.Snippet.Description += \"\\n\\nNotes:\\n\" + gd3.Notes;\r\n                }\r\n\r\n            }\r\n\r\n            if (settings.YouTubeDescriptionsExtra != null)\r\n            {\r\n                video.Snippet.Description += \"\\n\" + settings.YouTubeDescriptionsExtra;\r\n            }\r\n\r\n            video.Snippet.Description += \"\\n\\nVideo created using SidWizPlus - https://github.com/maxim-zhao/SidWizPlus\";\r\n\r\n            if (settings.YouTubeTagsFromGd3 && gd3 != null)\r\n            {\r\n                // We have a VGM file\r\n                tags.Add(gd3.Game.English);\r\n                tags.Add(gd3.System.English);\r\n                tags.AddRange(gd3.Composer.English.Split(';'));\r\n            }\r\n\r\n            video.Snippet.Tags = tags.Distinct().Where(t => !string.IsNullOrEmpty(t)).Select(t => t.Trim()).ToList();\r\n\r\n            if (settings.YouTubeCategory != null)\r\n            {\r\n                var request = youtubeService.VideoCategories.List(\"snippet\");\r\n                request.RegionCode = \"US\";\r\n                var response = await request.ExecuteAsync();\r\n                video.Snippet.CategoryId = response.Items\r\n                    .Where(c => c.Snippet.Title.ToLowerInvariant().Contains(settings.YouTubeCategory.ToLowerInvariant()))\r\n                    .Select(c => c.Id)\r\n                    .FirstOrDefault();\r\n                if (video.Snippet.CategoryId == null)\r\n                {\r\n                    await Console.Error.WriteLineAsync($\"Warning: couldn't find category matching \\\"{settings.YouTubeCategory}\\\", defaulting to \\\"Music\\\"\");\r\n                }\r\n            }\r\n\r\n            if (gd3 != null)\r\n            {\r\n                video.Snippet.Title = FormatFromGd3(video.Snippet.Title, gd3);\r\n            }\r\n\r\n            if (video.Snippet.Title.Length > 100)\r\n            {\r\n                video.Snippet.Title = video.Snippet.Title.Substring(0, 97) + \"...\";\r\n            }\r\n\r\n            // We now escape some strings as the API doesn't do it internally...\r\n            video.Snippet.Title = RemoveAngledBrackets(video.Snippet.Title);\r\n            video.Snippet.Description = RemoveAngledBrackets(video.Snippet.Description);\r\n            video.Snippet.Tags = video.Snippet.Tags.Select(RemoveAngledBrackets).ToList();\r\n\r\n            await UploadVideo(settings.OutputFile, youtubeService, video);\r\n\r\n            if (settings.YouTubePlaylist != null && !string.IsNullOrEmpty(video.Id))\r\n            {\r\n                if (gd3 != null)\r\n                {\r\n                    settings.YouTubePlaylist = RemoveAngledBrackets(FormatFromGd3(settings.YouTubePlaylist, gd3));\r\n                }\r\n                \r\n                // We need to decide if it's an existing playlist\r\n\r\n                // We iterate over all channels...\r\n                var playlistsRequest = youtubeService.Playlists.List(\"snippet\");\r\n                playlistsRequest.Mine = true;\r\n                var playlistsResponse = await playlistsRequest.ExecuteAsync();\r\n                var playlist = playlistsResponse.Items.FirstOrDefault(p => p.Snippet.Title == settings.YouTubePlaylist);\r\n                if (playlist == null)\r\n                {\r\n                    // Create it\r\n                    playlist = new Playlist\r\n                    {\r\n                        Snippet = new PlaylistSnippet\r\n                        {\r\n                            Title = settings.YouTubePlaylist\r\n                        },\r\n                        Status = new PlaylistStatus\r\n                        {\r\n                            PrivacyStatus = \"public\"\r\n                        }\r\n                    };\r\n                    if (settings.YouTubePlaylistDescriptionFile != null)\r\n                    {\r\n                        playlist.Snippet.Description = RemoveAngledBrackets(File.ReadAllText(settings.YouTubePlaylistDescriptionFile));\r\n                    }\r\n\r\n                    if (settings.YouTubeDescriptionsExtra != null)\r\n                    {\r\n                        playlist.Snippet.Description += \"\\n\\n\" + settings.YouTubeDescriptionsExtra;\r\n                    }\r\n\r\n                    playlist = await youtubeService.Playlists.Insert(playlist, \"snippet, status\").ExecuteAsync();\r\n                    Console.WriteLine($\"Created playlist \\\"{settings.YouTubePlaylist}\\\" with ID {playlist.Id}\");\r\n                }\r\n\r\n                // Add to it\r\n                var newPlaylistItem = new PlaylistItem\r\n                {\r\n                    Snippet = new PlaylistItemSnippet\r\n                    {\r\n                        PlaylistId = playlist.Id,\r\n                        ResourceId = new ResourceId {Kind = \"youtube#video\", VideoId = video.Id}\r\n                    }\r\n                };\r\n                newPlaylistItem = await youtubeService.PlaylistItems.Insert(newPlaylistItem, \"snippet\").ExecuteAsync();\r\n                Console.WriteLine($\"Added video {video.Id} ({video.Snippet.Title}) to playlist {playlist.Id} ({playlist.Snippet.Title}) as item {newPlaylistItem.Id}\");\r\n            }\r\n\r\n            return video.Id;\r\n        }\r\n\r\n        private static async Task UploadVideo(string filename, YouTubeService youtubeService, Video video)\r\n        {\r\n            using var fileStream = new FileStream(filename, FileMode.Open);\r\n            var videosInsertRequest = youtubeService.Videos.Insert(video, \"snippet,status\", fileStream, \"video/*\");\r\n            videosInsertRequest.ChunkSize = ResumableUpload.MinimumChunkSize;\r\n            long totalSize = fileStream.Length;\r\n            bool shouldRetry = true;\r\n            var sw = Stopwatch.StartNew();\r\n            videosInsertRequest.ProgressChanged += progress =>\r\n            {\r\n                switch (progress.Status)\r\n                {\r\n                    case UploadStatus.Uploading:\r\n                    {\r\n                        var elapsedSeconds = sw.Elapsed.TotalSeconds;\r\n                        var fractionComplete = (double)progress.BytesSent / totalSize;\r\n                        var eta = TimeSpan.FromSeconds(elapsedSeconds / fractionComplete - elapsedSeconds);\r\n                        var sent = (double)progress.BytesSent / 1024 / 1024;\r\n                        var kbPerSecond = progress.BytesSent / sw.Elapsed.TotalSeconds / 1024;\r\n                        Console.Write(\r\n                            $\"\\r{fractionComplete:P0} {sent:f}MB sent, average {kbPerSecond:f}KB/s, ETA {eta:g}\");\r\n                        break;\r\n                    }\r\n                    case UploadStatus.Failed:\r\n                        Console.Error.WriteLine($\"Upload failed: {progress.Exception}\");\r\n                        // Google API says we can retry if we get a non-API error, or one of these four 5xx error codes\r\n                        shouldRetry = progress.Exception is not GoogleApiException errorCode\r\n                                      || new[]\r\n                                      {\r\n                                          HttpStatusCode.InternalServerError, HttpStatusCode.BadGateway,\r\n                                          HttpStatusCode.ServiceUnavailable, HttpStatusCode.GatewayTimeout\r\n                                      }.Contains(errorCode.HttpStatusCode);\r\n                        if (shouldRetry)\r\n                        {\r\n                            Console.WriteLine(\"Retrying...\");\r\n                        }\r\n\r\n                        break;\r\n                    case UploadStatus.Completed:\r\n                        Console.WriteLine($\"Progress: {progress.Status}\");\r\n                        shouldRetry = false;\r\n                        break;\r\n                    default:\r\n                        Console.WriteLine($\"Progress: {progress.Status}\");\r\n                        break;\r\n                }\r\n            };\r\n            videosInsertRequest.ResponseReceived += video1 =>\r\n            {\r\n                video.Id = video1.Id;\r\n                Console.WriteLine($\"\\nUpload completed: video ID is {video1.Id}\");\r\n            };\r\n\r\n            try\r\n            {\r\n                await videosInsertRequest.UploadAsync();\r\n            }\r\n            catch (Exception ex)\r\n            {\r\n                await Console.Error.WriteLineAsync($\"Upload failed: {ex}\");\r\n            }\r\n\r\n            while (shouldRetry)\r\n            {\r\n                try\r\n                {\r\n                    await videosInsertRequest.ResumeAsync();\r\n                }\r\n                catch (Exception ex)\r\n                {\r\n                    await Console.Error.WriteLineAsync($\"Upload failed: {ex}\");\r\n                }\r\n            }\r\n        }\r\n\r\n        private static async Task<YouTubeService> GetYouTubeService(Settings settings)\r\n        {\r\n            if (settings.YouTubeUploadClientSecret == null)\r\n            {\r\n                throw new Exception(\"No YouTube client secret provided\");\r\n            }\r\n\r\n            var credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(\r\n                    (await GoogleClientSecrets.FromFileAsync(settings.YouTubeUploadClientSecret)).Secrets,\r\n                    // This OAuth 2.0 access scope allows an application to upload files to the\r\n                    // authenticated user's YouTube channel, but doesn't allow other types of access.\r\n                    [YouTubeService.Scope.YoutubeUpload, YouTubeService.Scope.YoutubeForceSsl],\r\n                    \"SidWizPlus\",\r\n                    CancellationToken.None\r\n                );\r\n\r\n                var youtubeService = new YouTubeService(new BaseClientService.Initializer\r\n                {\r\n                    HttpClientInitializer = credential,\r\n                    ApplicationName = Assembly.GetExecutingAssembly().GetName().Name,\r\n                    GZipEnabled = true\r\n                });\r\n                return youtubeService;\r\n        }\r\n\r\n        private static async Task UploadMergedToYouTube(Settings settings)\r\n        {\r\n            if (!File.Exists(settings.FfMpegPath))\r\n            {\r\n                settings.FfMpegPath = FindExecutable(\"ffmpeg.exe\");\r\n            }\r\n\r\n            // First we look for the videos and collect some metadata\r\n            var outputPath = Path.GetFullPath(settings.OutputFile);\r\n            var directoryName = Path.GetDirectoryName(settings.YouTubeMerge);\r\n            if (string.IsNullOrEmpty(directoryName))\r\n            {\r\n                directoryName = \".\";\r\n            }\r\n            var files = Directory.EnumerateFiles(\r\n                    directoryName,\r\n                    Path.GetFileName(settings.YouTubeMerge))\r\n                .AsParallel()\r\n                .Select(Path.GetFullPath)\r\n                .Where(path => path != outputPath)\r\n                .Select(path => new\r\n                {\r\n                    Path = path,\r\n                    Length = GetVideoDuration(path, settings),\r\n                    Gd3 = GetGd3(path)\r\n                })\r\n                .OrderBy(x => x.Path)\r\n                .ToList();\r\n\r\n            foreach (var file in files)\r\n            {\r\n                Console.WriteLine($\"{file.Path} is {file.Length}, tag is {file.Gd3?.ToString() ?? \"<unknown>\"}\");\r\n            }\r\n\r\n            var mergedGd3 = MergeGd3Tags(files.Select(x => x.Gd3).Where(x => x != null).ToList());\r\n\r\n            // Next we start to build the description with \"chapter markers\"\r\n            var description = new StringBuilder()\r\n                .AppendLine(\r\n                    $\"Oscilloscope View of music from the game {mergedGd3.Game} for the {mergedGd3.System}.\");\r\n            if (mergedGd3.Composer.English.Length > 0)\r\n            {\r\n                description.AppendLine($\"Composed by {mergedGd3.Composer}.\");\r\n            }\r\n\r\n            description.AppendLine($\"Ripped by {mergedGd3.Ripper}\");\r\n\r\n            description.AppendLine(\"\\nPlaylist:\");\r\n            var position = TimeSpan.Zero;\r\n            foreach (var file in files)\r\n            {\r\n                description.AppendLine($\"{position:hh':'mm':'ss} {file.Gd3?.Title.ToString() ?? Path.GetFileNameWithoutExtension(file.Path)}\");\r\n                position += file.Length;\r\n            }\r\n\r\n            if (settings.YouTubeDescriptionsExtra != null)\r\n            {\r\n                description.AppendLine($\"\\n{settings.YouTubeDescriptionsExtra}\");\r\n            }\r\n\r\n            description.AppendLine(\"\\nVideo created using SidWizPlus - https://github.com/maxim-zhao/SidWizPlus\");\r\n\r\n            Console.WriteLine(\"Video description:\");\r\n            Console.WriteLine(description);\r\n\r\n            // Now we merge the files...\r\n            // We need to write them to a list file for FFMPEG\r\n            var listFile = Path.GetTempFileName();\r\n            File.WriteAllLines(listFile, files.Select(f => $\"file '{f.Path.Replace(\"'\", \"'\\\\''\")}'\"));\r\n            using (var wrapper = new ProcessWrapper(\r\n                settings.FfMpegPath,\r\n                $\"-hide_banner -y -f concat -safe 0 -i \\\"{listFile}\\\" -c copy {settings.FfMpegExtraOptions} \\\"{settings.OutputFile}\\\"\",\r\n                false,\r\n                false,\r\n                true))\r\n            {\r\n                wrapper.WaitForExit();\r\n            }\r\n            File.Delete(listFile);\r\n\r\n            // Now we start the YouTube work...\r\n\r\n            var youtubeService = await GetYouTubeService(settings);\r\n\r\n            var video = new Video\r\n            {\r\n                Snippet = new VideoSnippet\r\n                {\r\n                    Title = FormatFromGd3(settings.YouTubeTitle, mergedGd3).TrimEnd(' ', '-'),\r\n                    CategoryId = \"10\", // Music\r\n                    Description = description.ToString()\r\n                },\r\n                Status = new VideoStatus {PrivacyStatus = \"public\"}\r\n            };\r\n\r\n            if (settings.YouTubeCategory != null)\r\n            {\r\n                bool retry = false;\r\n                int tryCount = 0;\r\n                do\r\n                {\r\n                    try\r\n                    {\r\n                        var request = youtubeService.VideoCategories.List(\"snippet\");\r\n                        request.RegionCode = \"US\";\r\n                        ++tryCount;\r\n                        var response = await request.ExecuteAsync();\r\n                        video.Snippet.CategoryId = response.Items\r\n                            .Where(c => c.Snippet.Title.ToLowerInvariant()\r\n                                .Contains(settings.YouTubeCategory.ToLowerInvariant()))\r\n                            .Select(c => c.Id)\r\n                            .FirstOrDefault();\r\n                        if (video.Snippet.CategoryId == null)\r\n                        {\r\n                            await Console.Error.WriteLineAsync(\r\n                                $\"Warning: couldn't find category matching \\\"{settings.YouTubeCategory}\\\", defaulting to \\\"Music\\\"\");\r\n                        }\r\n                    }\r\n                    catch (AggregateException ex)\r\n                    {\r\n                        retry = ex.InnerExceptions.OfType<TokenResponseException>().Any() && tryCount < 10;\r\n                        await Console.Error.WriteLineAsync($\"Exception talking to YouTube; retry = {retry}, tryCount = {tryCount}\");\r\n                    }\r\n                } while (retry);\r\n            }\r\n\r\n            if (video.Snippet.Title.Length > 100)\r\n            {\r\n                video.Snippet.Title = video.Snippet.Title.Substring(0, 97) + \"...\";\r\n            }\r\n\r\n            if (video.Snippet.Description.Length > 4500)\r\n            {\r\n                video.Snippet.Description = video.Snippet.Description.Substring(0, 4450) + \"...\\nDescription truncated to fit in YouTube limits\";\r\n            }\r\n\r\n            // We now escape some strings as the API doesn't do it internally...\r\n            video.Snippet.Title = RemoveAngledBrackets(video.Snippet.Title);\r\n            video.Snippet.Description = RemoveAngledBrackets(video.Snippet.Description);\r\n\r\n            await UploadVideo(settings.OutputFile, youtubeService, video);\r\n        }\r\n\r\n        private static Gd3Tag MergeGd3Tags(IList<Gd3Tag> tags)\r\n        {\r\n            return new Gd3Tag\r\n            {\r\n                System = new Gd3Tag.MultiLanguageTag\r\n                {\r\n                    English = MergeTags(tags, t => t.System.English),\r\n                    Japanese = MergeTags(tags, t => t.System.Japanese)\r\n                },\r\n                Game = new Gd3Tag.MultiLanguageTag\r\n                {\r\n                    English = MergeTags(tags, t => t.Game.English),\r\n                    Japanese = MergeTags(tags, t => t.Game.Japanese)\r\n                },\r\n                Title = new Gd3Tag.MultiLanguageTag\r\n                {\r\n                    English = MergeTags(tags, t => t.Title.English),\r\n                    Japanese = MergeTags(tags, t => t.Title.Japanese)\r\n                },\r\n                Composer = new Gd3Tag.MultiLanguageTag\r\n                {\r\n                    English = MergeTags(tags, t => t.Composer.English),\r\n                    Japanese = MergeTags(tags, t => t.Composer.Japanese)\r\n                },\r\n                Ripper = MergeTags(tags, t => t.Ripper),\r\n                Date = MergeTags(tags, t => t.Date)\r\n            };\r\n        }\r\n\r\n        private static string MergeTags(IEnumerable<Gd3Tag> tags, Func<Gd3Tag, string> getter)\r\n        {\r\n            return string.Join(\"; \",\r\n                tags.Select(getter)\r\n                    .SelectMany(s => s.Split(';', ','))\r\n                    .Select(s => s.Trim())\r\n                    .Where(s => s.Length > 0)\r\n                    .Distinct()\r\n                    .OrderBy(s => s));\r\n        }\r\n\r\n        private static Gd3Tag GetGd3(string path)\r\n        {\r\n            // We guess a file path for the VGM\r\n            var vgmPath = Path.ChangeExtension(path, \"vgm\");\r\n            if (!File.Exists(vgmPath))\r\n            {\r\n                return null;\r\n            }\r\n\r\n            return new VgmFile(vgmPath).Gd3Tag;\r\n        }\r\n\r\n        private static TimeSpan GetVideoDuration(string path, Settings settings)\r\n        {\r\n            // This is a bit of a hack...\r\n            var re = new Regex(\" +Duration: (?<duration>[0-9:.]+)\");\r\n            using var wrapper = new ProcessWrapper(\r\n                settings.FfMpegPath,\r\n                $\"-i \\\"{path}\\\" -hide_banner\",\r\n                true);\r\n            wrapper.WaitForExit();\r\n            var lines = wrapper.Lines().ToList();\r\n            var line = lines.FirstOrDefault(s => re.IsMatch(s)) \r\n                ?? throw new Exception($\"Failed to find duration for {path}. FFMPEG output:\\n{string.Join(\"\\n\", lines)}\");\r\n            return TimeSpan.Parse(re.Match(line).Groups[\"duration\"].Value);\r\n        }\r\n\r\n        private static string RemoveAngledBrackets(string s)\r\n        {\r\n            return s.Replace(\"<\", \"\").Replace(\">\", \"\");\r\n        }\r\n\r\n        private static string FormatFromGd3(string pattern, Gd3Tag gd3)\r\n        {\r\n            return pattern\r\n                .Replace(\"[title]\", gd3.Title.English)\r\n                .Replace(\"[system]\", gd3.System.English)\r\n                .Replace(\"[game]\", gd3.Game.English)\r\n                .Replace(\"[composer]\", gd3.Composer.English);\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "SidWizPlus/SidWizPlus.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\r\n  <PropertyGroup>\r\n    <TargetFramework>net48</TargetFramework>\r\n    <OutputType>Exe</OutputType>\r\n    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>\r\n    <LangVersion>latest</LangVersion>\r\n    <UseWindowsForms>true</UseWindowsForms>\r\n  </PropertyGroup>\r\n  <ItemGroup>\r\n    <ProjectReference Include=\"..\\LibSidWiz\\LibSidWiz.csproj\" />\r\n    <ProjectReference Include=\"..\\LibVgm\\LibVgm.csproj\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <PackageReference Include=\"CommandLineParser\" Version=\"2.9.1\" />\r\n    <PackageReference Include=\"Google.Apis.YouTube.v3\" Version=\"1.68.0.3421\" />\r\n  </ItemGroup>\r\n</Project>"
  },
  {
    "path": "SidWizPlus/TextInfo.cs",
    "content": "﻿using System.Drawing;\r\nusing System.Windows.Forms;\r\n\r\nnamespace SidWizPlus\r\n{\r\n    internal class TextInfo\r\n    {\r\n        public string Text { get; }\r\n        public string FontName { get; }\r\n        public float FontSize { get; }\r\n        public FontStyle FontStyle { get; }\r\n        public ContentAlignment Alignment { get; }\r\n        public DockStyle ConstrainWaves { get; }\r\n        public Color Color { get; }\r\n\r\n        public TextInfo(string text, string fontName, float fontSize, ContentAlignment alignment, FontStyle fontStyle, DockStyle constrainWaves, Color color)\r\n        {\r\n            Text = text;\r\n            Alignment = alignment;\r\n            ConstrainWaves = constrainWaves;\r\n            Color = color;\r\n            FontName = fontName;\r\n            FontSize = fontSize;\r\n            FontStyle = fontStyle;\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "appveyor.yml",
    "content": "version: '{build}'\nskip_tags: true\nimage: Visual Studio 2022\nconfiguration: Release\nenvironment:\n  secret:\n    secure: mFvP2wRhzLBcR2kQJuZw1pj5tpB0R17V4xSwO8VWuRLeLPQXn8AhyNgLjpnry/AoLdCQGMWxl94VlgdxfGlIl+QmhR+glo29vvPTQzyyKwCV4B5i/OvFngZ4PJX4225tlUr8lPEU+/JV82/FOscVwnXEEaOvp3NVegiCrrferZm0ufKd5Xy7L1noCAAPa3/r909r4rFcdA5cGQqLKb7t2S73hpM4zu7sj9ytxfgtg7ID/Hp1KXhkO9zS/OhCD3bTPIgqry4w1DIQ26U9yysSpFPfOePV3SOqNZWZjW2IcQ0ra6cwFL+6UQu8ZYFBWpL81mEM7+kCQYCh4XdXtpIEIVKSGG1qD7i4KWdziG4e/DBKDsIdNa0jxD0RJlU0sIVdIiPzf8mgMsbKgVWD/PmazGTkB2je9CdVOzQBMgAUkUs/6sthNTTEUCELdwc26TiHzGZYd5Tg+b99tjAp6jcoDAt2si85rEfFtk2q5ezbWQuUly41kOhd87C7/xEZRPSReM+VWt3MFCowK0UBpynxjj8lHp2fm8/PQOTRj6gJ3fQED8HbBfUrOLwvvEF/7U4QWs3atHhKcSWImNDk9QIfHg==\nbefore_build:\n- cmd: nuget restore\nbuild:\n  parallel: true\n  verbosity: minimal\nafter_build:\n- cmd: |\n    cd /d %APPVEYOR_BUILD_FOLDER%\\SidWizPlus\\bin\\Release\\net48\n    7z a -mx9 -r %APPVEYOR_BUILD_FOLDER%\\sidwizplus.zip *.dll *.exe\n    cd /d %APPVEYOR_BUILD_FOLDER%\\SidWiz\\bin\\Release\\net48\n    7z a -mx9 -r %APPVEYOR_BUILD_FOLDER%\\sidwizplusgui.zip *.dll *.exe\nartifacts:\n- path: '*.zip'\n  name: Binaries\ndeploy:\n- provider: GitHub\n  description: Appveyor automated build\n  auth_token:\n    secure: rKUfRHMGs7wVh/SYX9kJ67VqiPlztPwvnh3yjC3C2NX/Wpn9XsZrxIx0BXe9ZztT\n  artifact: Binaries\n  draft: false\n  prerelease: false\n  force_update: true"
  }
]