├── .gitattributes ├── .gitignore ├── LICENSE.md ├── README.md ├── VG Music Studio.sln └── VG Music Studio ├── AlphaDream.yaml ├── Config.yaml ├── Core ├── ADPCMDecoder.cs ├── Assembler.cs ├── Config.cs ├── Engine.cs ├── GBA │ ├── AlphaDream │ │ ├── Channel.cs │ │ ├── Commands.cs │ │ ├── Config.cs │ │ ├── Enums.cs │ │ ├── Mixer.cs │ │ ├── Player.cs │ │ ├── SoundFontSaver_DLS.cs │ │ ├── SoundFontSaver_SF2.cs │ │ ├── Structs.cs │ │ └── Track.cs │ ├── MP2K │ │ ├── Channel.cs │ │ ├── Commands.cs │ │ ├── Config.cs │ │ ├── Enums.cs │ │ ├── Mixer.cs │ │ ├── Player.cs │ │ ├── Structs.cs │ │ ├── Track.cs │ │ └── Utils.cs │ └── Utils.cs ├── GlobalConfig.cs ├── Mixer.cs ├── NDS │ ├── DSE │ │ ├── Channel.cs │ │ ├── Commands.cs │ │ ├── Config.cs │ │ ├── Enums.cs │ │ ├── Mixer.cs │ │ ├── Player.cs │ │ ├── SMD.cs │ │ ├── SWD.cs │ │ ├── Track.cs │ │ └── Utils.cs │ ├── SDAT │ │ ├── Channel.cs │ │ ├── Commands.cs │ │ ├── Config.cs │ │ ├── Enums.cs │ │ ├── FileHeader.cs │ │ ├── Mixer.cs │ │ ├── Player.cs │ │ ├── SBNK.cs │ │ ├── SDAT.cs │ │ ├── SSEQ.cs │ │ ├── SWAR.cs │ │ ├── Track.cs │ │ └── Utils.cs │ └── Utils.cs ├── Player.cs ├── SongEvent.cs └── VGMSDebug.cs ├── Dependencies ├── DLS2.dll ├── Sanford.Multimedia.Midi.dll └── SoundFont2.dll ├── MP2K.yaml ├── MPlayDef.s ├── Program.cs ├── Properties ├── AssemblyInfo.cs ├── Icon.ico ├── Icon16.png ├── Icon24.png ├── Icon32.png ├── Icon48.png ├── Icon528.png ├── Next.ico ├── Next.png ├── Pause.ico ├── Pause.png ├── Play.ico ├── Play.png ├── Playlist.png ├── Previous.ico ├── Previous.png ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs ├── Settings.settings ├── Song.png ├── Strings.Designer.cs ├── Strings.es.resx ├── Strings.it.resx └── Strings.resx ├── UI ├── ColorSlider.cs ├── FlexibleMessageBox.cs ├── ImageComboBox.cs ├── MainForm.cs ├── PianoControl.cs ├── SongInfoControl.cs ├── Theme.cs ├── TrackViewer.cs └── ValueTextBox.cs ├── Util ├── BetterExceptions.cs ├── HSLColor.cs ├── SampleUtils.cs ├── TimeBarrier.cs └── Utils.cs ├── VG Music Studio.csproj ├── midi2agb.exe └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | Build/ 25 | 26 | # Visual Studio 2015 cache/options directory 27 | .vs/ 28 | # Uncomment if you have tasks that create the project's static files in wwwroot 29 | #wwwroot/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | # NUNIT 36 | *.VisualState.xml 37 | TestResult.xml 38 | 39 | # Build Results of an ATL Project 40 | [Dd]ebugPS/ 41 | [Rr]eleasePS/ 42 | dlldata.c 43 | 44 | # DNX 45 | project.lock.json 46 | project.fragment.lock.json 47 | artifacts/ 48 | 49 | *_i.c 50 | *_p.c 51 | *_i.h 52 | *.ilk 53 | *.meta 54 | *.obj 55 | *.pch 56 | *.pdb 57 | *.pgc 58 | *.pgd 59 | *.rsp 60 | *.sbr 61 | *.tlb 62 | *.tli 63 | *.tlh 64 | *.tmp 65 | *.tmp_proj 66 | *.log 67 | *.vspscc 68 | *.vssscc 69 | .builds 70 | *.pidb 71 | *.svclog 72 | *.scc 73 | 74 | # Chutzpah Test files 75 | _Chutzpah* 76 | 77 | # Visual C++ cache files 78 | ipch/ 79 | *.aps 80 | *.ncb 81 | *.opendb 82 | *.opensdf 83 | *.sdf 84 | *.cachefile 85 | *.VC.db 86 | *.VC.VC.opendb 87 | 88 | # Visual Studio profiler 89 | *.psess 90 | *.vsp 91 | *.vspx 92 | *.sap 93 | 94 | # TFS 2012 Local Workspace 95 | $tf/ 96 | 97 | # Guidance Automation Toolkit 98 | *.gpState 99 | 100 | # ReSharper is a .NET coding add-in 101 | _ReSharper*/ 102 | *.[Rr]e[Ss]harper 103 | *.DotSettings.user 104 | 105 | # JustCode is a .NET coding add-in 106 | .JustCode 107 | 108 | # TeamCity is a build add-in 109 | _TeamCity* 110 | 111 | # DotCover is a Code Coverage Tool 112 | *.dotCover 113 | 114 | # NCrunch 115 | _NCrunch_* 116 | .*crunch*.local.xml 117 | nCrunchTemp_* 118 | 119 | # MightyMoose 120 | *.mm.* 121 | AutoTest.Net/ 122 | 123 | # Web workbench (sass) 124 | .sass-cache/ 125 | 126 | # Installshield output folder 127 | [Ee]xpress/ 128 | 129 | # DocProject is a documentation generator add-in 130 | DocProject/buildhelp/ 131 | DocProject/Help/*.HxT 132 | DocProject/Help/*.HxC 133 | DocProject/Help/*.hhc 134 | DocProject/Help/*.hhk 135 | DocProject/Help/*.hhp 136 | DocProject/Help/Html2 137 | DocProject/Help/html 138 | 139 | # Click-Once directory 140 | publish/ 141 | 142 | # Publish Web Output 143 | *.[Pp]ublish.xml 144 | *.azurePubxml 145 | # TODO: Comment the next line if you want to checkin your web deploy settings 146 | # but database connection strings (with potential passwords) will be unencrypted 147 | #*.pubxml 148 | *.publishproj 149 | 150 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 151 | # checkin your Azure Web App publish settings, but sensitive information contained 152 | # in these scripts will be unencrypted 153 | PublishScripts/ 154 | 155 | # NuGet Packages 156 | *.nupkg 157 | # The packages folder can be ignored because of Package Restore 158 | **/packages/* 159 | # except build/, which is used as an MSBuild target. 160 | !**/packages/build/ 161 | # Uncomment if necessary however generally it will be regenerated when needed 162 | #!**/packages/repositories.config 163 | # NuGet v3's project.json files produces more ignoreable files 164 | *.nuget.props 165 | *.nuget.targets 166 | 167 | # Microsoft Azure Build Output 168 | csx/ 169 | *.build.csdef 170 | 171 | # Microsoft Azure Emulator 172 | ecf/ 173 | rcf/ 174 | 175 | # Windows Store app package directories and files 176 | AppPackages/ 177 | BundleArtifacts/ 178 | Package.StoreAssociation.xml 179 | _pkginfo.txt 180 | 181 | # Visual Studio cache files 182 | # files ending in .cache can be ignored 183 | *.[Cc]ache 184 | # but keep track of directories ending in .cache 185 | !*.[Cc]ache/ 186 | 187 | # Others 188 | ClientBin/ 189 | ~$* 190 | *~ 191 | *.dbmdl 192 | *.dbproj.schemaview 193 | *.jfm 194 | *.pfx 195 | *.publishsettings 196 | node_modules/ 197 | orleans.codegen.cs 198 | 199 | # Since there are multiple workflows, uncomment next line to ignore bower_components 200 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 201 | #bower_components/ 202 | 203 | # RIA/Silverlight projects 204 | Generated_Code/ 205 | 206 | # Backup & report files from converting an old project file 207 | # to a newer Visual Studio version. Backup files are not needed, 208 | # because we have git ;-) 209 | _UpgradeReport_Files/ 210 | Backup*/ 211 | UpgradeLog*.XML 212 | UpgradeLog*.htm 213 | 214 | # SQL Server files 215 | *.mdf 216 | *.ldf 217 | 218 | # Business Intelligence projects 219 | *.rdl.data 220 | *.bim.layout 221 | *.bim_*.settings 222 | 223 | # Microsoft Fakes 224 | FakesAssemblies/ 225 | 226 | # GhostDoc plugin setting file 227 | *.GhostDoc.xml 228 | 229 | # Node.js Tools for Visual Studio 230 | .ntvs_analysis.dat 231 | 232 | # Visual Studio 6 build log 233 | *.plg 234 | 235 | # Visual Studio 6 workspace options file 236 | *.opt 237 | 238 | # Visual Studio LightSwitch build output 239 | **/*.HTMLClient/GeneratedArtifacts 240 | **/*.DesktopClient/GeneratedArtifacts 241 | **/*.DesktopClient/ModelManifest.xml 242 | **/*.Server/GeneratedArtifacts 243 | **/*.Server/ModelManifest.xml 244 | _Pvt_Extensions 245 | 246 | # Paket dependency manager 247 | .paket/paket.exe 248 | paket-files/ 249 | 250 | # FAKE - F# Make 251 | .fake/ 252 | 253 | # JetBrains Rider 254 | .idea/ 255 | *.sln.iml 256 | 257 | # CodeRush 258 | .cr/ 259 | 260 | # Python Tools for Visual Studio (PTVS) 261 | __pycache__/ 262 | *.pyc -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kermalis's VG Music Studio 2 | 3 | [![Join on Discord](https://discordapp.com/api/guilds/571068449103675394/widget.png?style=shield)][Discord] 4 | [![LatestVer](https://img.shields.io/github/v/release/Kermalis/VGMusicStudio.svg?include_prereleases)](https://github.com/Kermalis/VGMusicStudio/releases/latest) 5 | [![Releases](https://img.shields.io/github/downloads/Kermalis/VGMusicStudio/total.svg)](https://github.com/Kermalis/VGMusicStudio/releases/latest) 6 | [![License](https://img.shields.io/badge/License-LGPLv3-blue.svg)](LICENSE.md) 7 | 8 | VG Music Studio is a music player and visualizer for the most common GBA music format (MP2K), AlphaDream's GBA music format, the most common NDS music format (SDAT), and a more rare NDS/WII music format (DSE) [found in PMD2 among others]. 9 | 10 | [![VG Music Studio Preview](https://i.imgur.com/hWJGG83.png)](https://www.youtube.com/watch?v=s1BZ7cRbtBU "VG Music Studio Preview") 11 | 12 | If you want to talk or would like a game added to our configs, join our [Discord server][Discord] 13 | 14 | ---- 15 | ## To Do: 16 | ### General 17 | * MIDI saving - Preview the MIDI with the Sequencer class 18 | * MIDI saving - UI with saving options, such as remapping 19 | * MIDI saving - Make errors more clear 20 | * Voice table viewer - Tooltips which provide a huge chunk of information 21 | * Detachable piano 22 | * Tempo numerical (it fits) 23 | * Help dialog that explains the commands and config for each engine 24 | 25 | ### AlphaDream Engine 26 | * ADSR 27 | * Voice table - Find out the last 4 bytes in voice entry struct (probably ADSR) 28 | * PSG channels 3 and 4 29 | * Some more unknown commands 30 | * Tempo per track 31 | 32 | ### DSE Engine 33 | * ADSR 34 | * Pitch bend 35 | * LFO 36 | * Ability to load SMDB and SWDB (Big Endian as opposed to SMDL and SWDL for Little Endian) 37 | * Some more unknown commands 38 | 39 | ### MP2K Engine 40 | * Add Golden Sun 2 reverb effect 41 | * Add reverse playback 42 | * Add SquareWave sweeping 43 | * XCMD command 44 | * REPT command 45 | * Support pret dissassembly projects 46 | * Running status in song disassembler 47 | * Add "Metroid Fusion" & "Metroid: Zero Mission" engine information 48 | * Mario Power Tennis compressed samples 49 | 50 | ### SDAT Engine 51 | * Find proper formulas for LFO 52 | 53 | ---- 54 | ## Special Thanks To: 55 | ### General 56 | * Stich991 - Italian translation 57 | * tuku473 - Design suggestions, colors, Spanish translation 58 | 59 | ### AlphaDream Engine 60 | * irdkwia - Finding games that used the engine 61 | * Jesse (jelle) - Engine research 62 | * Platinum Lucario - Engine research 63 | 64 | ### DSE Engine 65 | * PsyCommando - Extensive research [(and his DSE music tools)](https://github.com/PsyCommando/ppmdu) 66 | 67 | ### MP2K Engine 68 | * Bregalad - Extensive documentation 69 | * Ipatix - Engine research, help, [(and his MP2K music player)](https://github.com/ipatix/agbplay) from which some of my code is based on 70 | * mimi - Told me about a hidden feature of the engine 71 | * SomeShrug - Engine research and helped me understand more about the engine parameters 72 | 73 | ### SDAT Engine 74 | * kiwi.ds SDAT Specification - Extensive documentation 75 | 76 | ---- 77 | ## VG Music Studio Uses: 78 | * [DLS2](https://github.com/Kermalis/DLS2) 79 | * [EndianBinaryIO](https://github.com/Kermalis/EndianBinaryIO) 80 | * [NAudio](https://github.com/naudio/NAudio) 81 | * [ObjectListView](http://objectlistview.sourceforge.net) 82 | * [My fork of Sanford.Multimedia.Midi](https://github.com/Kermalis/Sanford.Multimedia.Midi) 83 | * [SoundFont2](https://github.com/Kermalis/SoundFont2) 84 | * [YamlDotNet](https://github.com/aaubry/YamlDotNet/wiki) 85 | 86 | [Discord]: https://discord.gg/mBQXCTs -------------------------------------------------------------------------------- /VG Music Studio.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.2002 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VG Music Studio", "VG Music Studio\VG Music Studio.csproj", "{97C8ACF8-66A3-4321-91D6-3E94EACA577F}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {97C8ACF8-66A3-4321-91D6-3E94EACA577F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {97C8ACF8-66A3-4321-91D6-3E94EACA577F}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {97C8ACF8-66A3-4321-91D6-3E94EACA577F}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {97C8ACF8-66A3-4321-91D6-3E94EACA577F}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {4858DADE-EB1D-47C1-8033-425E0A805959} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /VG Music Studio/AlphaDream.yaml: -------------------------------------------------------------------------------- 1 | A88E_00: 2 | Name: "Mario & Luigi - Superstar Saga (USA)" 3 | AudioEngineVersion: "MLSS" 4 | SongTableOffsets: 0x21CB70 5 | SongTableSizes: 407 6 | VoiceTableOffset: 0x21D1CC 7 | SampleTableOffset: 0xA806B8 8 | SampleTableSize: 236 9 | Remap: "MLSS" 10 | Playlists: 11 | Music: 12 | 1: "1" 13 | 2: "2" 14 | 3: "Mini Game" 15 | 4: "Border Jump" 16 | 5: "Star 'Stache Smash" 17 | 6: "6" 18 | 7: "7" 19 | 8: "Stardust Fields" 20 | 9: "Hoohoo Mountain" 21 | 10: "Battle" 22 | 11: "Victory" 23 | 12: "Beanbean Fields" 24 | 13: "Chucklehuck Woods" 25 | 14: "Seabed" 26 | 15: "Beanbean Castle" 27 | 16: "Boss Battle" 28 | 17: "Woohoo Hooniversity" 29 | 18: "Teehee Valley" 30 | 19: "Joke's End" 31 | 20: "Little Fungitown" 32 | 21: "Gwarhar Lagoon" 33 | 22: "Underground" 34 | 23: "Toad Town Square" 35 | 24: "Bowser's Castle" 36 | 25: "Warp Pipe" 37 | 26: "Special Item" 38 | 27: "Royal Welcome" 39 | 28: "Prince Peasley's Theme" 40 | 29: "Cackletta's Theme" 41 | 30: "File Select" 42 | 31: "Hoohoo Village" 43 | 32: "Devastation" 44 | 33: "Panic!" 45 | 34: "Koopa Cruiser" 46 | 35: "Danger!" 47 | 36: "Popple Battle" 48 | 37: "Cackletta Battle" 49 | 38: "Bowletta Battle" 50 | 39: "Ending" 51 | 40: "Credits" 52 | 41: "Title" 53 | 42: "Chateau de Chucklehuck" 54 | 43: "43" 55 | 44: "Final Cackletta Battle" 56 | 45: "45" 57 | 46: "46" 58 | 47: "47" 59 | 48: "Professor E Gadd" 60 | 49: "Ghostly Encounter" 61 | 50: "Bean Time!" 62 | A88J_00: 63 | Name: "Mario & Luigi - Superstar Saga (Japan)" 64 | SongTableOffsets: 0x205060 65 | SongTableSizes: 418 66 | VoiceTableOffset: 0x2056E8 67 | SampleTableOffset: 0x9721A8 68 | SampleTableSize: 239 69 | Copy: "A88E_00" 70 | A88P_00: 71 | Name: "Mario & Luigi - Superstar Saga (Europe)" 72 | SongTableOffsets: 0x21DC50 73 | VoiceTableOffset: 0x21E2AC 74 | SampleTableOffset: 0xA82088 75 | Copy: "A88E_00" 76 | A84P_00: 77 | Name: "Hamtaro - Rainbow Rescue (Europe)" 78 | AudioEngineVersion: "Hamtaro" 79 | SongTableOffsets: 0x694300 80 | SongTableSizes: 150 81 | VoiceTableOffset: 0x694558 82 | SampleTableOffset: 0xBCD510 83 | SampleTableSize: 149 84 | Playlists: 85 | Music: 86 | 1: "Title" 87 | 2: "Menu" 88 | 3: "Stickers" 89 | 4: "Coloring" 90 | 5: "The Clubhouse" 91 | 6: "Ham-Ham Lawn" 92 | 7: "Final Credits" 93 | 8: "Bo" 94 | 9: "Sunny Peak" 95 | 10: "Flower Ranch" 96 | 11: "Clover Elementary" 97 | 12: "Ticky-Ticky Park" 98 | 13: "Tip-Top Fair" 99 | 14: "Taiko Drumming" 100 | 15: "Sparkle Coast" 101 | 16: "Aquarium" 102 | 17: "Hamstarr Manor" 103 | 18: "Puntaros" 104 | 19: "Acorn Trail / Sea Spray Park" 105 | 20: "Rainbow Theater" 106 | 21: "Ham Game #1" 107 | 22: "Ham Game #2" 108 | 23: "Ham Game #3" 109 | 24: "Ham Game #4" 110 | 25: "Credits" 111 | 26: "Ham-Ham Lawn (again)" 112 | 27: "Ham-Ham March" 113 | 28: "That's Not Good..." 114 | 29: "Uh-Oh..." 115 | 30: "Success!" 116 | 31: "Oh No!" 117 | 32: "Headband-Ham" 118 | 33: "Bittersweet" 119 | 34: "We did it!" 120 | 35: "Pigeon Ride" 121 | 36: "Boss" 122 | 37: "Hamstarr" 123 | 38: "Jingle" 124 | 39: "Carrobo" 125 | A84J_00: 126 | Name: "Hamtaro - Rainbow Rescue (Japan)" 127 | SongTableOffsets: 0x4DDE20 128 | VoiceTableOffset: 0x4DE078 129 | SampleTableOffset: 0x742FFC 130 | Copy: "A84P_00" 131 | B85A_00: 132 | Name: "Hamtaro - Ham-Ham Games (Japan/USA)" 133 | AudioEngineVersion: "Hamtaro" 134 | SongTableOffsets: 0xDED20 135 | SongTableSizes: 212 136 | VoiceTableOffset: 0xDF10C 137 | SampleTableOffset: 0x100000 138 | SampleTableSize: 368 139 | Playlists: 140 | Music: 141 | 1: "Title" 142 | 2: "Menu" 143 | 3: "The Clubhouse" 144 | 4: "Cards / Crystal" 145 | 5: "Introduction" 146 | 6: "Credits" 147 | 7: "Hamigo" 148 | 8: "Event Preparation / Results" 149 | 9: "Synchronized Swimming" 150 | 10: "Other Events" 151 | 11: "Marathon #1" 152 | 12: "Marathon #2" 153 | 13: "Marathon #3" 154 | 14: "Marathon #4" 155 | 15: "Marathon #5" 156 | 16: "Results Ceremony" 157 | 17: "Ham Shopping Network" 158 | 18: "Ham Studio News" 159 | 19: "Ham Fortune Telling" 160 | 20: "Ham Studio Shows Introduction" 161 | 21: "BGM #1" 162 | 22: "BGM #2" 163 | 23: "BGM #3" 164 | 24: "BGM #4" 165 | 25: "BGM #5" 166 | 26: "BGM #6" 167 | 27: "BGM #7" 168 | 28: "BGM #8" 169 | 29: "BGM #9" 170 | 30: "BGM #10" 171 | 31: "BGM #11" 172 | 32: "Stadium Events" 173 | 33: "BGM #12" 174 | 34: "BGM #13" 175 | 35: "BGM #14" 176 | 36: "BGM #15" 177 | 37: "BGM #16" 178 | 38: "BGM #17" 179 | 39: "BGM #18" 180 | 40: "BGM #19" 181 | 41: "BGM #20" 182 | 42: "42" 183 | 43: "BGM #21" 184 | 44: "BGM #22" 185 | 45: "BGM #23" 186 | 46: "BGM #24" 187 | 47: "BGM #25" 188 | 48: "BGM #26" 189 | 49: "BGM #27" 190 | 50: "BGM #28" 191 | 51: "BGM #29" 192 | 52: "BGM #30" 193 | 53: "Minigame A" 194 | 54: "Minigame B" 195 | 55: "Carrobo" 196 | B85P_00: 197 | Name: "Hamtaro - Ham-Ham Games (Europe)" 198 | SongTableOffsets: 0xDED20 199 | VoiceTableOffset: 0xDF10C 200 | SampleTableOffset: 0x100000 201 | Copy: "B85A_00" 202 | -------------------------------------------------------------------------------- /VG Music Studio/Config.yaml: -------------------------------------------------------------------------------- 1 | TaskbarProgress: True # True or False # Whether the taskbar will show the song progress 2 | RefreshRate: 30 # RefreshRate >= 1 and RefreshRate <= 1000 # How many times a second the visual updates 3 | CenterIndicators: False # True or False # Whether lines should be drawn for the center of panpot in the visual 4 | PanpotIndicators: False # True or False # Whether lines should be drawn for the track's panpot in the visual 5 | PlaylistMode: "Random" # "Random" or "Sequential" # The way the playlist will behave 6 | PlaylistSongLoops: 0 # Loops >= 0 and Loops <= 9223372036854775807 # How many times a song should loop before fading out 7 | PlaylistFadeOutMilliseconds: 10000 # Milliseconds >= 0 and Milliseconds <= 9223372036854775807 # How many milliseconds it should take to fade out of a song 8 | MiddleCOctave: 4 # Octave >= --128 and Octave <= 127 # The octave that holds middle C. Used in the visual and track viewer 9 | Colors: 10 | 0: {H: 185, S: 240, L: 180} 11 | 1: {H: 183, S: 240, L: 170} 12 | 2: {H: 180, S: 240, L: 157} 13 | 3: {H: 184, S: 240, L: 85} 14 | 4: {H: 171, S: 240, L: 134} 15 | 5: {H: 168, S: 240, L: 159} 16 | 6: {H: 36, S: 240, L: 170} 17 | 7: {H: 15, S: 240, L: 134} 18 | 8: {H: 175, S: 240, L: 200} 19 | 9: {H: 120, S: 240, L: 150} 20 | 10: {H: 114, S: 240, L: 138} 21 | 11: {H: 99, S: 240, L: 171} 22 | 12: {H: 68, S: 240, L: 171} 23 | 13: {H: 83, S: 240, L: 200} 24 | 14: {H: 215, S: 240, L: 104} 25 | 15: {H: 25, S: 240, L: 200} 26 | 16: {H: 224, S: 240, L: 150} 27 | 17: {H: 195, S: 240, L: 120} 28 | 18: {H: 206, S: 240, L: 95} 29 | 19: {H: 218, S: 240, L: 129} 30 | 20: {H: 203, S: 240, L: 180} 31 | 21: {H: 145, S: 240, L: 100} 32 | 22: {H: 140, S: 240, L: 111} 33 | 23: {H: 151, S: 240, L: 120} 34 | 24: {H: 5, S: 240, L: 169} 35 | 25: {H: 6, S: 240, L: 156} 36 | 26: {H: 14, S: 240, L: 164} 37 | 27: {H: 12, S: 240, L: 137} 38 | 28: {H: 8, S: 240, L: 140} 39 | 29: {H: 0, S: 240, L: 123} 40 | 30: {H: 229, S: 240, L: 70} 41 | 31: {H: 239, S: 240, L: 89} 42 | 32: {H: 25, S: 180, L: 160} 43 | 33: {H: 20, S: 180, L: 145} 44 | 34: {H: 17, S: 180, L: 140} 45 | 35: {H: 36, S: 240, L: 163} 46 | 36: {H: 25, S: 180, L: 140} 47 | 37: {H: 25, S: 210, L: 95} 48 | 38: {H: 160, S: 0, L: 180} 49 | 39: {H: 200, S: 240, L: 90} 50 | 40: {H: 195, S: 240, L: 100} 51 | 41: {H: 190, S: 240, L: 93} 52 | 42: {H: 180, S: 240, L: 90} 53 | 43: {H: 170, S: 240, L: 150} 54 | 44: {H: 166, S: 240, L: 89} 55 | 45: {H: 210, S: 240, L: 170} 56 | 46: {H: 214, S: 240, L: 185} 57 | 47: {H: 15, S: 135, L: 135} 58 | 48: {H: 148, S: 240, L: 130} 59 | 49: {H: 173, S: 240, L: 80} 60 | 50: {H: 170, S: 240, L: 95} 61 | 51: {H: 176, S: 240, L: 100} 62 | 52: {H: 26, S: 240, L: 215} 63 | 53: {H: 20, S: 240, L: 210} 64 | 54: {H: 5, S: 240, L: 220} 65 | 55: {H: 6, S: 240, L: 150} 66 | 56: {H: 22, S: 240, L: 134} 67 | 57: {H: 25, S: 240, L: 130} 68 | 58: {H: 40, S: 240, L: 120} 69 | 59: {H: 28, S: 240, L: 122} 70 | 60: {H: 16, S: 240, L: 124} 71 | 61: {H: 11, S: 240, L: 118} 72 | 62: {H: 53, S: 240, L: 158} 73 | 63: {H: 57, S: 240, L: 133} 74 | 64: {H: 30, S: 240, L: 195} 75 | 65: {H: 23, S: 240, L: 182} 76 | 66: {H: 32, S: 240, L: 160} 77 | 67: {H: 32, S: 240, L: 130} 78 | 68: {H: 37, S: 240, L: 135} 79 | 69: {H: 13, S: 240, L: 143} 80 | 70: {H: 134, S: 240, L: 85} 81 | 71: {H: 130, S: 240, L: 95} 82 | 72: {H: 120, S: 240, L: 165} 83 | 73: {H: 126, S: 240, L: 120} 84 | 74: {H: 126, S: 240, L: 100} 85 | 75: {H: 135, S: 240, L: 160} 86 | 76: {H: 118, S: 240, L: 186} 87 | 77: {H: 135, S: 240, L: 102} 88 | 78: {H: 113, S: 240, L: 100} 89 | 79: {H: 70, S: 240, L: 160} 90 | 80: {H: 82, S: 240, L: 132} 91 | 81: {H: 227, S: 240, L: 188} 92 | 82: {H: 103, S: 240, L: 140} 93 | 83: {H: 60, S: 240, L: 165} 94 | 84: {H: 239, S: 240, L: 165} 95 | 85: {H: 123, S: 240, L: 175} 96 | 86: {H: 210, S: 240, L: 145} 97 | 87: {H: 53, S: 240, L: 120} 98 | 88: {H: 110, S: 240, L: 155} 99 | 89: {H: 122, S: 240, L: 205} 100 | 90: {H: 217, S: 240, L: 95} 101 | 91: {H: 142, S: 240, L: 50} 102 | 92: {H: 100, S: 240, L: 90} 103 | 93: {H: 137, S: 240, L: 90} 104 | 94: {H: 188, S: 240, L: 117} 105 | 95: {H: 160, S: 240, L: 210} 106 | 96: {H: 130, S: 240, L: 200} 107 | 97: {H: 202, S: 240, L: 80} 108 | 98: {H: 0, S: 240, L: 160} 109 | 99: {H: 30, S: 240, L: 110} 110 | 100: {H: 130, S: 240, L: 210} 111 | 101: {H: 75, S: 240, L: 75} 112 | 102: {H: 180, S: 240, L: 205} 113 | 103: {H: 27, S: 200, L: 105} 114 | 104: {H: 33, S: 200, L: 145} 115 | 105: {H: 37, S: 220, L: 130} 116 | 106: {H: 45, S: 240, L: 135} 117 | 107: {H: 55, S: 240, L: 175} 118 | 108: {H: 95, S: 240, L: 185} 119 | 109: {H: 53, S: 240, L: 190} 120 | 110: {H: 135, S: 240, L: 120} 121 | 111: {H: 38, S: 240, L: 110} 122 | 112: {H: 220, S: 240, L: 170} 123 | 113: {H: 120, S: 80, L: 150} 124 | 114: {H: 130, S: 120, L: 190} 125 | 115: {H: 0, S: 80, L: 90} 126 | 116: {H: 18, S: 125, L: 130} 127 | 117: {H: 15, S: 70, L: 120} 128 | 118: {H: 200, S: 80, L: 110} 129 | 119: {H: 140, S: 60, L: 180} 130 | 120: {H: 10, S: 240, L: 90} 131 | 121: {H: 123, S: 156, L: 100} 132 | 122: {H: 128, S: 240, L: 100} 133 | 123: {H: 40, S: 240, L: 180} 134 | 124: {H: 239, S: 200, L: 90} 135 | 125: {H: 145, S: 10, L: 155} 136 | 126: {H: 15, S: 80, L: 160} 137 | 127: {H: 160, S: 80, L: 150} -------------------------------------------------------------------------------- /VG Music Studio/Core/ADPCMDecoder.cs: -------------------------------------------------------------------------------- 1 | namespace Kermalis.VGMusicStudio.Core 2 | { 3 | internal class ADPCMDecoder 4 | { 5 | private static readonly short[] _indexTable = new short[8] 6 | { 7 | -1, -1, -1, -1, 2, 4, 6, 8 8 | }; 9 | private static readonly short[] _stepTable = new short[89] 10 | { 11 | 00007, 00008, 00009, 00010, 00011, 00012, 00013, 00014, 12 | 00016, 00017, 00019, 00021, 00023, 00025, 00028, 00031, 13 | 00034, 00037, 00041, 00045, 00050, 00055, 00060, 00066, 14 | 00073, 00080, 00088, 00097, 00107, 00118, 00130, 00143, 15 | 00157, 00173, 00190, 00209, 00230, 00253, 00279, 00307, 16 | 00337, 00371, 00408, 00449, 00494, 00544, 00598, 00658, 17 | 00724, 00796, 00876, 00963, 01060, 01166, 01282, 01411, 18 | 01552, 01707, 01878, 02066, 02272, 02499, 02749, 03024, 19 | 03327, 03660, 04026, 04428, 04871, 05358, 05894, 06484, 20 | 07132, 07845, 08630, 09493, 10442, 11487, 12635, 13899, 21 | 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 22 | 32767 23 | }; 24 | 25 | private readonly byte[] _data; 26 | public short LastSample; 27 | public short StepIndex; 28 | public int DataOffset; 29 | public bool OnSecondNibble; 30 | 31 | public ADPCMDecoder(byte[] data) 32 | { 33 | LastSample = (short)(data[0] | (data[1] << 8)); 34 | StepIndex = (short)((data[2] | (data[3] << 8)) & 0x7F); 35 | DataOffset = 4; 36 | _data = data; 37 | } 38 | 39 | public static short[] ADPCMToPCM16(byte[] data) 40 | { 41 | var decoder = new ADPCMDecoder(data); 42 | short[] buffer = new short[(data.Length - 4) * 2]; 43 | for (int i = 0; i < buffer.Length; i++) 44 | { 45 | buffer[i] = decoder.GetSample(); 46 | } 47 | return buffer; 48 | } 49 | 50 | public short GetSample() 51 | { 52 | int val = (_data[DataOffset] >> (OnSecondNibble ? 4 : 0)) & 0xF; 53 | short step = _stepTable[StepIndex]; 54 | int diff = 55 | (step / 8) + 56 | (step / 4 * (val & 1)) + 57 | (step / 2 * ((val >> 1) & 1)) + 58 | (step * ((val >> 2) & 1)); 59 | 60 | int a = (diff * ((((val >> 3) & 1) == 1) ? -1 : 1)) + LastSample; 61 | if (a < short.MinValue) 62 | { 63 | a = short.MinValue; 64 | } 65 | else if (a > short.MaxValue) 66 | { 67 | a = short.MaxValue; 68 | } 69 | LastSample = (short)a; 70 | 71 | a = StepIndex + _indexTable[val & 7]; 72 | if (a < 0) 73 | { 74 | a = 0; 75 | } 76 | else if (a > 88) 77 | { 78 | a = 88; 79 | } 80 | StepIndex = (short)a; 81 | 82 | if (OnSecondNibble) 83 | { 84 | DataOffset++; 85 | } 86 | OnSecondNibble = !OnSecondNibble; 87 | return LastSample; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /VG Music Studio/Core/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | 6 | namespace Kermalis.VGMusicStudio.Core 7 | { 8 | internal abstract class Config : IDisposable 9 | { 10 | public class Song 11 | { 12 | public long Index; 13 | public string Name; 14 | 15 | public Song(long index, string name) 16 | { 17 | Index = index; Name = name; 18 | } 19 | 20 | public override bool Equals(object obj) 21 | { 22 | return obj is Song other && other.Index == Index; 23 | } 24 | public override int GetHashCode() 25 | { 26 | return Index.GetHashCode(); 27 | } 28 | public override string ToString() 29 | { 30 | return Name; 31 | } 32 | } 33 | public class Playlist 34 | { 35 | public string Name; 36 | public List Songs; 37 | 38 | public Playlist(string name, IEnumerable songs) 39 | { 40 | Name = name; Songs = songs.ToList(); 41 | } 42 | 43 | public override string ToString() 44 | { 45 | int songCount = Songs.Count; 46 | CultureInfo cul = System.Threading.Thread.CurrentThread.CurrentUICulture; 47 | if (cul.TwoLetterISOLanguageName == "it") // Italian 48 | { 49 | // PlaylistName - (1 Canzone) 50 | // PlaylistName - (2 Canzoni) 51 | return $"{Name} - ({songCount} {(songCount == 1 ? "Canzone" : "Canzoni")})"; 52 | } 53 | else if (cul.TwoLetterISOLanguageName == "es") // Spanish 54 | { 55 | // PlaylistName - (1 Canción) 56 | // PlaylistName - (2 Canciones) 57 | return $"{Name} - ({songCount} {(songCount == 1 ? "Canción" : "Canciones")})"; 58 | } 59 | else // Fallback to en-US 60 | { 61 | // PlaylistName - (1 Song) 62 | // PlaylistName - (2 Songs) 63 | return $"{Name} - ({songCount} {(songCount == 1 ? "Song" : "Songs")})"; 64 | } 65 | } 66 | } 67 | 68 | public List Playlists = new List(); 69 | 70 | public Song GetFirstSong(long index) 71 | { 72 | foreach (Playlist p in Playlists) 73 | { 74 | foreach (Song s in p.Songs) 75 | { 76 | if (s.Index == index) 77 | { 78 | return s; 79 | } 80 | } 81 | } 82 | return null; 83 | } 84 | 85 | public abstract string GetGameName(); 86 | public abstract string GetSongName(long index); 87 | 88 | public virtual void Dispose() { } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /VG Music Studio/Core/Engine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Kermalis.VGMusicStudio.Core 4 | { 5 | internal class Engine : IDisposable 6 | { 7 | public enum EngineType : byte 8 | { 9 | None, 10 | GBA_AlphaDream, 11 | GBA_MP2K, 12 | NDS_DSE, 13 | NDS_SDAT 14 | } 15 | 16 | public static Engine Instance { get; private set; } 17 | 18 | public EngineType Type { get; } 19 | public Config Config { get; private set; } 20 | public Mixer Mixer { get; private set; } 21 | public IPlayer Player { get; private set; } 22 | 23 | public Engine(EngineType type, object playerArg) 24 | { 25 | switch (type) 26 | { 27 | case EngineType.GBA_AlphaDream: 28 | { 29 | byte[] rom = (byte[])playerArg; 30 | if (rom.Length > GBA.Utils.CartridgeCapacity) 31 | { 32 | throw new Exception($"The ROM is too large. Maximum size is 0x{GBA.Utils.CartridgeCapacity:X7} bytes."); 33 | } 34 | var config = new GBA.AlphaDream.Config(rom); 35 | Config = config; 36 | var mixer = new GBA.AlphaDream.Mixer(config); 37 | Mixer = mixer; 38 | Player = new GBA.AlphaDream.Player(mixer, config); 39 | break; 40 | } 41 | case EngineType.GBA_MP2K: 42 | { 43 | byte[] rom = (byte[])playerArg; 44 | if (rom.Length > GBA.Utils.CartridgeCapacity) 45 | { 46 | throw new Exception($"The ROM is too large. Maximum size is 0x{GBA.Utils.CartridgeCapacity:X7} bytes."); 47 | } 48 | var config = new GBA.MP2K.Config(rom); 49 | Config = config; 50 | var mixer = new GBA.MP2K.Mixer(config); 51 | Mixer = mixer; 52 | Player = new GBA.MP2K.Player(mixer, config); 53 | break; 54 | } 55 | case EngineType.NDS_DSE: 56 | { 57 | string bgmPath = (string)playerArg; 58 | var config = new NDS.DSE.Config(bgmPath); 59 | Config = config; 60 | var mixer = new NDS.DSE.Mixer(); 61 | Mixer = mixer; 62 | Player = new NDS.DSE.Player(mixer, config); 63 | break; 64 | } 65 | case EngineType.NDS_SDAT: 66 | { 67 | var sdat = (NDS.SDAT.SDAT)playerArg; 68 | var config = new NDS.SDAT.Config(sdat); 69 | Config = config; 70 | var mixer = new NDS.SDAT.Mixer(); 71 | Mixer = mixer; 72 | Player = new NDS.SDAT.Player(mixer, config); 73 | break; 74 | } 75 | default: throw new ArgumentOutOfRangeException(nameof(type)); 76 | } 77 | Type = type; 78 | Instance = this; 79 | } 80 | 81 | public void Dispose() 82 | { 83 | Config.Dispose(); 84 | Config = null; 85 | Mixer.Dispose(); 86 | Mixer = null; 87 | Player.Dispose(); 88 | Player = null; 89 | Instance = null; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /VG Music Studio/Core/GBA/AlphaDream/Channel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Kermalis.VGMusicStudio.Core.GBA.AlphaDream 4 | { 5 | internal abstract class Channel 6 | { 7 | protected readonly Mixer _mixer; 8 | public EnvelopeState State; 9 | public byte Key; 10 | public bool Stopped; 11 | 12 | protected ADSR _adsr; 13 | 14 | protected byte _velocity; 15 | protected int _pos; 16 | protected float _interPos; 17 | protected float _frequency; 18 | protected byte _leftVol; 19 | protected byte _rightVol; 20 | 21 | protected Channel(Mixer mixer) 22 | { 23 | _mixer = mixer; 24 | } 25 | 26 | public ChannelVolume GetVolume() 27 | { 28 | const float max = 0x10000; 29 | return new ChannelVolume 30 | { 31 | LeftVol = _leftVol * _velocity / max, 32 | RightVol = _rightVol * _velocity / max 33 | }; 34 | } 35 | public void SetVolume(byte vol, sbyte pan) 36 | { 37 | _leftVol = (byte)((vol * (-pan + 0x80)) >> 8); 38 | _rightVol = (byte)((vol * (pan + 0x80)) >> 8); 39 | } 40 | public abstract void SetPitch(int pitch); 41 | 42 | public abstract void Process(float[] buffer); 43 | } 44 | internal class PCMChannel : Channel 45 | { 46 | private SampleHeader _sampleHeader; 47 | private int _sampleOffset; 48 | private bool _bFixed; 49 | 50 | public PCMChannel(Mixer mixer) : base(mixer) { } 51 | public void Init(byte key, ADSR adsr, int sampleOffset, bool bFixed) 52 | { 53 | _velocity = adsr.A; 54 | State = EnvelopeState.Attack; 55 | _pos = 0; _interPos = 0; 56 | Key = key; 57 | _adsr = adsr; 58 | _sampleHeader = _mixer.Config.Reader.ReadObject(sampleOffset); 59 | _sampleOffset = sampleOffset + 0x10; 60 | _bFixed = bFixed; 61 | Stopped = false; 62 | } 63 | 64 | public override void SetPitch(int pitch) 65 | { 66 | if (_sampleHeader != null) 67 | { 68 | _frequency = (_sampleHeader.SampleRate >> 10) * (float)Math.Pow(2, ((Key - 60) / 12f) + (pitch / 768f)); 69 | } 70 | } 71 | 72 | private void StepEnvelope() 73 | { 74 | switch (State) 75 | { 76 | case EnvelopeState.Attack: 77 | { 78 | int nextVel = _velocity + _adsr.A; 79 | if (nextVel >= 0xFF) 80 | { 81 | State = EnvelopeState.Decay; 82 | _velocity = 0xFF; 83 | } 84 | else 85 | { 86 | _velocity = (byte)nextVel; 87 | } 88 | break; 89 | } 90 | case EnvelopeState.Decay: 91 | { 92 | int nextVel = (_velocity * _adsr.D) >> 8; 93 | if (nextVel <= _adsr.S) 94 | { 95 | State = EnvelopeState.Sustain; 96 | _velocity = _adsr.S; 97 | } 98 | else 99 | { 100 | _velocity = (byte)nextVel; 101 | } 102 | break; 103 | } 104 | case EnvelopeState.Release: 105 | { 106 | int next = (_velocity * _adsr.R) >> 8; 107 | if (next < 0) 108 | { 109 | next = 0; 110 | } 111 | _velocity = (byte)next; 112 | break; 113 | } 114 | } 115 | } 116 | 117 | public override void Process(float[] buffer) 118 | { 119 | StepEnvelope(); 120 | 121 | ChannelVolume vol = GetVolume(); 122 | float interStep = (_bFixed ? _sampleHeader.SampleRate >> 10 : _frequency) * _mixer.SampleRateReciprocal; 123 | int bufPos = 0; int samplesPerBuffer = _mixer.SamplesPerBuffer; 124 | do 125 | { 126 | float samp = (_mixer.Config.ROM[_pos + _sampleOffset] - 0x80) / (float)0x80; 127 | 128 | buffer[bufPos++] += samp * vol.LeftVol; 129 | buffer[bufPos++] += samp * vol.RightVol; 130 | 131 | _interPos += interStep; 132 | int posDelta = (int)_interPos; 133 | _interPos -= posDelta; 134 | _pos += posDelta; 135 | if (_pos >= _sampleHeader.Length) 136 | { 137 | if (_sampleHeader.DoesLoop == 0x40000000) 138 | { 139 | _pos = _sampleHeader.LoopOffset; 140 | } 141 | else 142 | { 143 | Stopped = true; 144 | break; 145 | } 146 | } 147 | } while (--samplesPerBuffer > 0); 148 | } 149 | } 150 | internal class SquareChannel : Channel 151 | { 152 | private float[] _pat; 153 | 154 | public SquareChannel(Mixer mixer) : base(mixer) { } 155 | public void Init(byte key, ADSR env, byte vol, sbyte pan, int pitch) 156 | { 157 | _pat = MP2K.Utils.SquareD50; // TODO 158 | Key = key; 159 | _adsr = env; 160 | SetVolume(vol, pan); 161 | SetPitch(pitch); 162 | State = EnvelopeState.Attack; 163 | } 164 | 165 | public override void SetPitch(int pitch) 166 | { 167 | _frequency = 3520 * (float)Math.Pow(2, ((Key - 69) / 12f) + (pitch / 768f)); 168 | } 169 | 170 | private void StepEnvelope() 171 | { 172 | switch (State) 173 | { 174 | case EnvelopeState.Attack: 175 | { 176 | int next = _velocity + _adsr.A; 177 | if (next >= 0xF) 178 | { 179 | State = EnvelopeState.Decay; 180 | _velocity = 0xF; 181 | } 182 | else 183 | { 184 | _velocity = (byte)next; 185 | } 186 | break; 187 | } 188 | case EnvelopeState.Decay: 189 | { 190 | int next = (_velocity * _adsr.D) >> 3; 191 | if (next <= _adsr.S) 192 | { 193 | State = EnvelopeState.Sustain; 194 | _velocity = _adsr.S; 195 | } 196 | else 197 | { 198 | _velocity = (byte)next; 199 | } 200 | break; 201 | } 202 | case EnvelopeState.Release: 203 | { 204 | int next = (_velocity * _adsr.R) >> 3; 205 | if (next < 0) 206 | { 207 | next = 0; 208 | } 209 | _velocity = (byte)next; 210 | break; 211 | } 212 | } 213 | } 214 | 215 | public override void Process(float[] buffer) 216 | { 217 | StepEnvelope(); 218 | 219 | ChannelVolume vol = GetVolume(); 220 | float interStep = _frequency * _mixer.SampleRateReciprocal; 221 | 222 | int bufPos = 0; int samplesPerBuffer = _mixer.SamplesPerBuffer; 223 | do 224 | { 225 | float samp = _pat[_pos]; 226 | 227 | buffer[bufPos++] += samp * vol.LeftVol; 228 | buffer[bufPos++] += samp * vol.RightVol; 229 | 230 | _interPos += interStep; 231 | int posDelta = (int)_interPos; 232 | _interPos -= posDelta; 233 | _pos = (_pos + posDelta) & 0x7; 234 | } while (--samplesPerBuffer > 0); 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /VG Music Studio/Core/GBA/AlphaDream/Commands.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | 3 | namespace Kermalis.VGMusicStudio.Core.GBA.AlphaDream 4 | { 5 | internal class FinishCommand : ICommand 6 | { 7 | public Color Color => Color.MediumSpringGreen; 8 | public string Label => "Finish"; 9 | public string Arguments => string.Empty; 10 | } 11 | internal class FreeNoteHamtaroCommand : ICommand // TODO: When optimization comes, get rid of free note vs note and just have the label differ 12 | { 13 | public Color Color => Color.SkyBlue; 14 | public string Label => "Free Note"; 15 | public string Arguments => $"{Util.Utils.GetNoteName(Key)} {Volume} {Duration}"; 16 | 17 | public byte Key { get; set; } 18 | public byte Volume { get; set; } 19 | public byte Duration { get; set; } 20 | } 21 | internal class FreeNoteMLSSCommand : ICommand 22 | { 23 | public Color Color => Color.SkyBlue; 24 | public string Label => "Free Note"; 25 | public string Arguments => $"{Util.Utils.GetNoteName(Key)} {Duration}"; 26 | 27 | public byte Key { get; set; } 28 | public byte Duration { get; set; } 29 | } 30 | internal class JumpCommand : ICommand 31 | { 32 | public Color Color => Color.MediumSpringGreen; 33 | public string Label => "Jump"; 34 | public string Arguments => $"0x{Offset:X7}"; 35 | 36 | public int Offset { get; set; } 37 | } 38 | internal class NoteHamtaroCommand : ICommand 39 | { 40 | public Color Color => Color.SkyBlue; 41 | public string Label => "Note"; 42 | public string Arguments => $"{Util.Utils.GetNoteName(Key)} {Volume} {Duration}"; 43 | 44 | public byte Key { get; set; } 45 | public byte Volume { get; set; } 46 | public byte Duration { get; set; } 47 | } 48 | internal class NoteMLSSCommand : ICommand 49 | { 50 | public Color Color => Color.SkyBlue; 51 | public string Label => "Note"; 52 | public string Arguments => $"{Util.Utils.GetNoteName(Key)} {Duration}"; 53 | 54 | public byte Key { get; set; } 55 | public byte Duration { get; set; } 56 | } 57 | internal class PanpotCommand : ICommand 58 | { 59 | public Color Color => Color.GreenYellow; 60 | public string Label => "Panpot"; 61 | public string Arguments => Panpot.ToString(); 62 | 63 | public sbyte Panpot { get; set; } 64 | } 65 | internal class PitchBendCommand : ICommand 66 | { 67 | public Color Color => Color.MediumPurple; 68 | public string Label => "Pitch Bend"; 69 | public string Arguments => Bend.ToString(); 70 | 71 | public sbyte Bend { get; set; } 72 | } 73 | internal class PitchBendRangeCommand : ICommand 74 | { 75 | public Color Color => Color.MediumPurple; 76 | public string Label => "Pitch Bend Range"; 77 | public string Arguments => Range.ToString(); 78 | 79 | public byte Range { get; set; } 80 | } 81 | internal class RestCommand : ICommand 82 | { 83 | public Color Color => Color.PaleVioletRed; 84 | public string Label => "Rest"; 85 | public string Arguments => Rest.ToString(); 86 | 87 | public byte Rest { get; set; } 88 | } 89 | internal class TrackTempoCommand : ICommand 90 | { 91 | public Color Color => Color.DeepSkyBlue; 92 | public string Label => "Track Tempo"; 93 | public string Arguments => Tempo.ToString(); 94 | 95 | public byte Tempo { get; set; } 96 | } 97 | internal class VoiceCommand : ICommand 98 | { 99 | public Color Color => Color.DarkSalmon; 100 | public string Label => "Voice"; 101 | public string Arguments => Voice.ToString(); 102 | 103 | public byte Voice { get; set; } 104 | } 105 | internal class VolumeCommand : ICommand 106 | { 107 | public Color Color => Color.SteelBlue; 108 | public string Label => "Volume"; 109 | public string Arguments => Volume.ToString(); 110 | 111 | public byte Volume { get; set; } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /VG Music Studio/Core/GBA/AlphaDream/Enums.cs: -------------------------------------------------------------------------------- 1 | namespace Kermalis.VGMusicStudio.Core.GBA.AlphaDream 2 | { 3 | internal enum AudioEngineVersion : byte 4 | { 5 | Hamtaro, 6 | MLSS 7 | } 8 | 9 | internal enum EnvelopeState : byte 10 | { 11 | Attack, 12 | Decay, 13 | Sustain, 14 | Release 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /VG Music Studio/Core/GBA/AlphaDream/Mixer.cs: -------------------------------------------------------------------------------- 1 | using NAudio.Wave; 2 | using System; 3 | 4 | namespace Kermalis.VGMusicStudio.Core.GBA.AlphaDream 5 | { 6 | internal class Mixer : Core.Mixer 7 | { 8 | public readonly float SampleRateReciprocal; 9 | private readonly float _samplesReciprocal; 10 | public readonly int SamplesPerBuffer; 11 | private bool _isFading; 12 | private long _fadeMicroFramesLeft; 13 | private float _fadePos; 14 | private float _fadeStepPerMicroframe; 15 | 16 | public readonly Config Config; 17 | private readonly WaveBuffer _audio; 18 | private readonly float[][] _trackBuffers = new float[Player.NumTracks][]; 19 | private readonly BufferedWaveProvider _buffer; 20 | 21 | public Mixer(Config config) 22 | { 23 | Config = config; 24 | const int sampleRate = 13379; // TODO: Actual value unknown 25 | SamplesPerBuffer = 224; // TODO 26 | SampleRateReciprocal = 1f / sampleRate; 27 | _samplesReciprocal = 1f / SamplesPerBuffer; 28 | 29 | int amt = SamplesPerBuffer * 2; 30 | _audio = new WaveBuffer(amt * sizeof(float)) { FloatBufferCount = amt }; 31 | for (int i = 0; i < Player.NumTracks; i++) 32 | { 33 | _trackBuffers[i] = new float[amt]; 34 | } 35 | _buffer = new BufferedWaveProvider(WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, 2)) // TODO 36 | { 37 | DiscardOnBufferOverflow = true, 38 | BufferLength = SamplesPerBuffer * 64 39 | }; 40 | Init(_buffer); 41 | } 42 | public override void Dispose() 43 | { 44 | base.Dispose(); 45 | CloseWaveWriter(); 46 | } 47 | 48 | public void BeginFadeIn() 49 | { 50 | _fadePos = 0f; 51 | _fadeMicroFramesLeft = (long)(GlobalConfig.Instance.PlaylistFadeOutMilliseconds / 1000.0 * Utils.AGB_FPS); 52 | _fadeStepPerMicroframe = 1f / _fadeMicroFramesLeft; 53 | _isFading = true; 54 | } 55 | public void BeginFadeOut() 56 | { 57 | _fadePos = 1f; 58 | _fadeMicroFramesLeft = (long)(GlobalConfig.Instance.PlaylistFadeOutMilliseconds / 1000.0 * Utils.AGB_FPS); 59 | _fadeStepPerMicroframe = -1f / _fadeMicroFramesLeft; 60 | _isFading = true; 61 | } 62 | public bool IsFading() 63 | { 64 | return _isFading; 65 | } 66 | public bool IsFadeDone() 67 | { 68 | return _isFading && _fadeMicroFramesLeft == 0; 69 | } 70 | public void ResetFade() 71 | { 72 | _isFading = false; 73 | _fadeMicroFramesLeft = 0; 74 | } 75 | 76 | private WaveFileWriter _waveWriter; 77 | public void CreateWaveWriter(string fileName) 78 | { 79 | _waveWriter = new WaveFileWriter(fileName, _buffer.WaveFormat); 80 | } 81 | public void CloseWaveWriter() 82 | { 83 | _waveWriter?.Dispose(); 84 | } 85 | public void Process(Track[] tracks, bool output, bool recording) 86 | { 87 | _audio.Clear(); 88 | float masterStep; 89 | float masterLevel; 90 | if (_isFading && _fadeMicroFramesLeft == 0) 91 | { 92 | masterStep = 0; 93 | masterLevel = 0; 94 | } 95 | else 96 | { 97 | float fromMaster = 1f; 98 | float toMaster = 1f; 99 | if (_fadeMicroFramesLeft > 0) 100 | { 101 | const float scale = 10f / 6f; 102 | fromMaster *= (_fadePos < 0f) ? 0f : (float)Math.Pow(_fadePos, scale); 103 | _fadePos += _fadeStepPerMicroframe; 104 | toMaster *= (_fadePos < 0f) ? 0f : (float)Math.Pow(_fadePos, scale); 105 | _fadeMicroFramesLeft--; 106 | } 107 | masterStep = (toMaster - fromMaster) * _samplesReciprocal; 108 | masterLevel = fromMaster; 109 | } 110 | for (int i = 0; i < Player.NumTracks; i++) 111 | { 112 | Track track = tracks[i]; 113 | if (track.Enabled && track.NoteDuration != 0 && !track.Channel.Stopped && !Mutes[i]) 114 | { 115 | float level = masterLevel; 116 | float[] buf = _trackBuffers[i]; 117 | Array.Clear(buf, 0, buf.Length); 118 | track.Channel.Process(buf); 119 | for (int j = 0; j < SamplesPerBuffer; j++) 120 | { 121 | _audio.FloatBuffer[j * 2] += buf[j * 2] * level; 122 | _audio.FloatBuffer[(j * 2) + 1] += buf[(j * 2) + 1] * level; 123 | level += masterStep; 124 | } 125 | } 126 | } 127 | if (output) 128 | { 129 | _buffer.AddSamples(_audio.ByteBuffer, 0, _audio.ByteBufferCount); 130 | } 131 | if (recording) 132 | { 133 | _waveWriter.Write(_audio.ByteBuffer, 0, _audio.ByteBufferCount); 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /VG Music Studio/Core/GBA/AlphaDream/SoundFontSaver_DLS.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.DLS2; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | 6 | namespace Kermalis.VGMusicStudio.Core.GBA.AlphaDream 7 | { 8 | internal sealed class SoundFontSaver_DLS 9 | { 10 | // Since every key will use the same articulation data, just store one instance 11 | private static readonly Level2ArticulatorChunk _art2 = new Level2ArticulatorChunk 12 | { 13 | new Level2ArticulatorConnectionBlock { Destination = Level2ArticulatorDestination.LFOFrequency, Scale = 2786 }, 14 | new Level2ArticulatorConnectionBlock { Destination = Level2ArticulatorDestination.VIBFrequency, Scale = 2786 }, 15 | new Level2ArticulatorConnectionBlock { Source = Level2ArticulatorSource.KeyNumber, Destination = Level2ArticulatorDestination.Pitch }, 16 | new Level2ArticulatorConnectionBlock { Source = Level2ArticulatorSource.Vibrato, Control = Level2ArticulatorSource.Modulation_CC1, Destination = Level2ArticulatorDestination.Pitch, BipolarSource = true, Scale = 0x320000 }, 17 | new Level2ArticulatorConnectionBlock { Source = Level2ArticulatorSource.Vibrato, Control = Level2ArticulatorSource.ChannelPressure, Destination = Level2ArticulatorDestination.Pitch, BipolarSource = true, Scale = 0x320000 }, 18 | new Level2ArticulatorConnectionBlock { Source = Level2ArticulatorSource.Pan_CC10, Destination = Level2ArticulatorDestination.Pan, BipolarSource = true, Scale = 0xFE0000 }, 19 | new Level2ArticulatorConnectionBlock { Source = Level2ArticulatorSource.ChorusSend_CC91, Destination = Level2ArticulatorDestination.Reverb, Scale = 0xC80000 }, 20 | new Level2ArticulatorConnectionBlock { Source = Level2ArticulatorSource.Reverb_SendCC93, Destination = Level2ArticulatorDestination.Chorus, Scale = 0xC80000 } 21 | }; 22 | 23 | public static void Save(Config config, string path) 24 | { 25 | var dls = new DLS(); 26 | AddInfo(config, dls); 27 | Dictionary sampleDict = AddSamples(config, dls); 28 | AddInstruments(config, dls, sampleDict); 29 | dls.Save(path); 30 | } 31 | 32 | private static void AddInfo(Config config, DLS dls) 33 | { 34 | var info = new ListChunk("INFO"); 35 | dls.Add(info); 36 | info.Add(new InfoSubChunk("INAM", config.Name)); 37 | //info.Add(new InfoSubChunk("ICOP", config.Creator)); 38 | info.Add(new InfoSubChunk("IENG", "Kermalis")); 39 | info.Add(new InfoSubChunk("ISFT", Util.Utils.ProgramName)); 40 | } 41 | 42 | private static Dictionary AddSamples(Config config, DLS dls) 43 | { 44 | ListChunk waves = dls.WavePool; 45 | var sampleDict = new Dictionary((int)config.SampleTableSize); 46 | for (int i = 0; i < config.SampleTableSize; i++) 47 | { 48 | int ofs = config.Reader.ReadInt32(config.SampleTableOffset + (i * 4)); 49 | if (ofs == 0) 50 | { 51 | continue; // Skip null samples 52 | } 53 | 54 | ofs += config.SampleTableOffset; 55 | SampleHeader sh = config.Reader.ReadObject(ofs); 56 | 57 | // Create format chunk 58 | var fmt = new FormatChunk(WaveFormat.PCM); 59 | fmt.WaveInfo.Channels = 1; 60 | fmt.WaveInfo.SamplesPerSec = (uint)(sh.SampleRate >> 10); 61 | fmt.WaveInfo.AvgBytesPerSec = fmt.WaveInfo.SamplesPerSec; 62 | fmt.WaveInfo.BlockAlign = 1; 63 | fmt.FormatInfo.BitsPerSample = 8; 64 | // Create wave sample chunk and add loop if there is one 65 | var wsmp = new WaveSampleChunk 66 | { 67 | UnityNote = 60, 68 | Options = WaveSampleOptions.NoTruncation | WaveSampleOptions.NoCompression 69 | }; 70 | if (sh.DoesLoop == 0x40000000) 71 | { 72 | wsmp.Loop = new WaveSampleLoop 73 | { 74 | LoopStart = (uint)sh.LoopOffset, 75 | LoopLength = (uint)(sh.Length - sh.LoopOffset), 76 | LoopType = LoopType.Forward 77 | }; 78 | } 79 | // Get PCM sample 80 | byte[] pcm = new byte[sh.Length]; 81 | Array.Copy(config.ROM, ofs + 0x10, pcm, 0, sh.Length); 82 | 83 | // Add 84 | int dlsIndex = waves.Count; 85 | waves.Add(new ListChunk("wave") 86 | { 87 | fmt, 88 | wsmp, 89 | new DataChunk(pcm), 90 | new ListChunk("INFO") 91 | { 92 | new InfoSubChunk("INAM", $"Sample {i}") 93 | } 94 | }); 95 | sampleDict.Add(i, (wsmp, dlsIndex)); 96 | } 97 | return sampleDict; 98 | } 99 | 100 | private static void AddInstruments(Config config, DLS dls, Dictionary sampleDict) 101 | { 102 | ListChunk lins = dls.InstrumentList; 103 | for (int v = 0; v < 256; v++) 104 | { 105 | short off = config.Reader.ReadInt16(config.VoiceTableOffset + (v * 2)); 106 | short nextOff = config.Reader.ReadInt16(config.VoiceTableOffset + ((v + 1) * 2)); 107 | int numEntries = (nextOff - off) / 8; // Each entry is 8 bytes 108 | if (numEntries == 0) 109 | { 110 | continue; // Skip empty entries 111 | } 112 | 113 | var ins = new ListChunk("ins "); 114 | ins.Add(new InstrumentHeaderChunk 115 | { 116 | NumRegions = (uint)numEntries, 117 | Locale = new MIDILocale(0, (byte)(v / 128), false, (byte)(v % 128)) 118 | }); 119 | var lrgn = new ListChunk("lrgn"); 120 | ins.Add(lrgn); 121 | ins.Add(new ListChunk("INFO") 122 | { 123 | new InfoSubChunk("INAM", $"Instrument {v}") 124 | }); 125 | lins.Add(ins); 126 | for (int e = 0; e < numEntries; e++) 127 | { 128 | VoiceEntry entry = config.Reader.ReadObject(config.VoiceTableOffset + off + (e * 8)); 129 | // Sample 130 | if (entry.Sample >= config.SampleTableSize) 131 | { 132 | Debug.WriteLine(string.Format("Voice {0} uses an invalid sample id ({1})", v, entry.Sample)); 133 | continue; 134 | } 135 | if (!sampleDict.TryGetValue(entry.Sample, out (WaveSampleChunk, int) value)) 136 | { 137 | Debug.WriteLine(string.Format("Voice {0} uses a null sample id ({1})", v, entry.Sample)); 138 | continue; 139 | } 140 | void Add(ushort low, ushort high, ushort baseKey) 141 | { 142 | var rgnh = new RegionHeaderChunk(); 143 | rgnh.KeyRange.Low = low; 144 | rgnh.KeyRange.High = high; 145 | lrgn.Add(new ListChunk("rgn2") 146 | { 147 | rgnh, 148 | new WaveSampleChunk 149 | { 150 | UnityNote = baseKey, 151 | Options = WaveSampleOptions.NoTruncation | WaveSampleOptions.NoCompression, 152 | Loop = value.Item1.Loop 153 | }, 154 | new WaveLinkChunk 155 | { 156 | Channels = WaveLinkChannels.Left, 157 | TableIndex = (uint)value.Item2 158 | }, 159 | new ListChunk("lar2") 160 | { 161 | _art2 162 | } 163 | }); 164 | } 165 | // Fixed frequency - Since DLS does not support it, we need to manually add every key with its own base note 166 | if (entry.IsFixedFrequency == 0x80) 167 | { 168 | for (ushort i = entry.MinKey; i <= entry.MaxKey; i++) 169 | { 170 | Add(i, i, i); 171 | } 172 | } 173 | else 174 | { 175 | Add(entry.MinKey, entry.MaxKey, 60); 176 | } 177 | } 178 | } 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /VG Music Studio/Core/GBA/AlphaDream/SoundFontSaver_SF2.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.SoundFont2; 2 | using Kermalis.VGMusicStudio.Util; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | 6 | namespace Kermalis.VGMusicStudio.Core.GBA.AlphaDream 7 | { 8 | internal sealed class SoundFontSaver_SF2 9 | { 10 | public static void Save(Config config, string path) 11 | { 12 | var sf2 = new SF2(); 13 | AddInfo(config, sf2.InfoChunk); 14 | Dictionary sampleDict = AddSamples(config, sf2); 15 | AddInstruments(config, sf2, sampleDict); 16 | sf2.Save(path); 17 | } 18 | 19 | private static void AddInfo(Config config, InfoListChunk chunk) 20 | { 21 | chunk.Bank = config.Name; 22 | //chunk.Copyright = config.Creator; 23 | chunk.Tools = Util.Utils.ProgramName + " by Kermalis"; 24 | } 25 | 26 | private static Dictionary AddSamples(Config config, SF2 sf2) 27 | { 28 | var sampleDict = new Dictionary((int)config.SampleTableSize); 29 | for (int i = 0; i < config.SampleTableSize; i++) 30 | { 31 | int ofs = config.Reader.ReadInt32(config.SampleTableOffset + (i * 4)); 32 | if (ofs == 0) 33 | { 34 | continue; 35 | } 36 | 37 | ofs += config.SampleTableOffset; 38 | SampleHeader sh = config.Reader.ReadObject(ofs); 39 | 40 | short[] pcm16 = SampleUtils.PCMU8ToPCM16(config.ROM, ofs + 0x10, sh.Length); 41 | int sf2Index = (int)sf2.AddSample(pcm16, $"Sample {i}", sh.DoesLoop == 0x40000000, (uint)sh.LoopOffset, (uint)(sh.SampleRate >> 10), 60, 0); 42 | sampleDict.Add(i, (sh, sf2Index)); 43 | } 44 | return sampleDict; 45 | } 46 | private static void AddInstruments(Config config, SF2 sf2, Dictionary sampleDict) 47 | { 48 | for (int v = 0; v < 256; v++) 49 | { 50 | short off = config.Reader.ReadInt16(config.VoiceTableOffset + (v * 2)); 51 | short nextOff = config.Reader.ReadInt16(config.VoiceTableOffset + ((v + 1) * 2)); 52 | int numEntries = (nextOff - off) / 8; // Each entry is 8 bytes 53 | if (numEntries == 0) 54 | { 55 | continue; 56 | } 57 | 58 | string name = "Instrument " + v; 59 | sf2.AddPreset(name, (ushort)v, 0); 60 | sf2.AddPresetBag(); 61 | sf2.AddPresetGenerator(SF2Generator.Instrument, new SF2GeneratorAmount { Amount = (short)sf2.AddInstrument(name) }); 62 | for (int e = 0; e < numEntries; e++) 63 | { 64 | VoiceEntry entry = config.Reader.ReadObject(config.VoiceTableOffset + off + (e * 8)); 65 | sf2.AddInstrumentBag(); 66 | // Key range 67 | if (!(entry.MinKey == 0 && entry.MaxKey == 0x7F)) 68 | { 69 | sf2.AddInstrumentGenerator(SF2Generator.KeyRange, new SF2GeneratorAmount { LowByte = entry.MinKey, HighByte = entry.MaxKey }); 70 | } 71 | // Fixed frequency 72 | if (entry.IsFixedFrequency == 0x80) 73 | { 74 | sf2.AddInstrumentGenerator(SF2Generator.ScaleTuning, new SF2GeneratorAmount { Amount = 0 }); 75 | } 76 | // Sample 77 | if (entry.Sample < config.SampleTableSize) 78 | { 79 | if (!sampleDict.TryGetValue(entry.Sample, out (SampleHeader, int) value)) 80 | { 81 | Debug.WriteLine(string.Format("Voice {0} uses a null sample id ({1})", v, entry.Sample)); 82 | } 83 | else 84 | { 85 | sf2.AddInstrumentGenerator(SF2Generator.SampleModes, new SF2GeneratorAmount { Amount = (short)(value.Item1.DoesLoop == 0x40000000 ? 1 : 0) }); 86 | sf2.AddInstrumentGenerator(SF2Generator.SampleID, new SF2GeneratorAmount { UAmount = (ushort)value.Item2 }); 87 | } 88 | } 89 | else 90 | { 91 | Debug.WriteLine(string.Format("Voice {0} uses an invalid sample id ({1})", v, entry.Sample)); 92 | } 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /VG Music Studio/Core/GBA/AlphaDream/Structs.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.EndianBinaryIO; 2 | 3 | namespace Kermalis.VGMusicStudio.Core.GBA.AlphaDream 4 | { 5 | internal class SampleHeader 6 | { 7 | /// 0x40000000 if True 8 | public int DoesLoop { get; set; } 9 | /// Right shift 10 for value 10 | public int SampleRate { get; set; } 11 | public int LoopOffset { get; set; } 12 | public int Length { get; set; } 13 | } 14 | internal class VoiceEntry 15 | { 16 | public byte MinKey { get; set; } 17 | public byte MaxKey { get; set; } 18 | public byte Sample { get; set; } 19 | /// 0x80 if True 20 | public byte IsFixedFrequency { get; set; } 21 | [BinaryArrayFixedLength(4)] 22 | public byte[] Unknown { get; set; } 23 | } 24 | 25 | internal struct ChannelVolume 26 | { 27 | public float LeftVol, RightVol; 28 | } 29 | internal class ADSR // TODO 30 | { 31 | public byte A, D, S, R; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /VG Music Studio/Core/GBA/AlphaDream/Track.cs: -------------------------------------------------------------------------------- 1 | namespace Kermalis.VGMusicStudio.Core.GBA.AlphaDream 2 | { 3 | internal class Track 4 | { 5 | public readonly byte Index; 6 | public readonly string Type; 7 | public readonly Channel Channel; 8 | 9 | public byte Voice; 10 | public byte PitchBendRange; 11 | public byte Volume; 12 | public byte Rest; 13 | public byte NoteDuration; 14 | public sbyte PitchBend; 15 | public sbyte Panpot; 16 | public bool Enabled; 17 | public bool Stopped; 18 | public int StartOffset; 19 | public int DataOffset; 20 | public byte PrevCommand; 21 | 22 | public int GetPitch() 23 | { 24 | return PitchBend * (PitchBendRange / 2); 25 | } 26 | 27 | public Track(byte i, Mixer mixer) 28 | { 29 | Index = i; 30 | if (i >= 8) 31 | { 32 | Type = Utils.PSGTypes[i & 3]; 33 | Channel = new SquareChannel(mixer); // TODO: PSG Channels 3 and 4 34 | } 35 | else 36 | { 37 | Type = "PCM8"; 38 | Channel = new PCMChannel(mixer); 39 | } 40 | } 41 | // 0x819B040 42 | public void Init() 43 | { 44 | Voice = 0; 45 | Rest = 1; // Unsure why Rest starts at 1 46 | PitchBendRange = 2; 47 | NoteDuration = 0; 48 | PitchBend = 0; 49 | Panpot = 0; // Start centered; ROM sets this to 0x7F since it's unsigned there 50 | DataOffset = StartOffset; 51 | Stopped = false; 52 | Volume = 200; 53 | PrevCommand = 0xFF; 54 | //Tempo = 120; 55 | //TempoStack = 0; 56 | } 57 | public void Tick() 58 | { 59 | if (Rest != 0) 60 | { 61 | Rest--; 62 | } 63 | if (NoteDuration > 0) 64 | { 65 | NoteDuration--; 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /VG Music Studio/Core/GBA/MP2K/Commands.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | 3 | namespace Kermalis.VGMusicStudio.Core.GBA.MP2K 4 | { 5 | internal class CallCommand : ICommand 6 | { 7 | public Color Color => Color.MediumSpringGreen; 8 | public string Label => "Call"; 9 | public string Arguments => $"0x{Offset:X7}"; 10 | 11 | public int Offset { get; set; } 12 | } 13 | internal class EndOfTieCommand : ICommand 14 | { 15 | public Color Color => Color.SkyBlue; 16 | public string Label => "End Of Tie"; 17 | public string Arguments => Key == -1 ? "All Ties" : Util.Utils.GetNoteName(Key); 18 | 19 | public int Key { get; set; } 20 | } 21 | internal class FinishCommand : ICommand 22 | { 23 | public Color Color => Color.MediumSpringGreen; 24 | public string Label => "Finish"; 25 | public string Arguments => Prev ? "Resume previous track" : "End track"; 26 | 27 | public bool Prev { get; set; } 28 | } 29 | internal class JumpCommand : ICommand 30 | { 31 | public Color Color => Color.MediumSpringGreen; 32 | public string Label => "Jump"; 33 | public string Arguments => $"0x{Offset:X7}"; 34 | 35 | public int Offset { get; set; } 36 | } 37 | internal class LFODelayCommand : ICommand 38 | { 39 | public Color Color => Color.LightSteelBlue; 40 | public string Label => "LFO Delay"; 41 | public string Arguments => Delay.ToString(); 42 | 43 | public byte Delay { get; set; } 44 | } 45 | internal class LFODepthCommand : ICommand 46 | { 47 | public Color Color => Color.LightSteelBlue; 48 | public string Label => "LFO Depth"; 49 | public string Arguments => Depth.ToString(); 50 | 51 | public byte Depth { get; set; } 52 | } 53 | internal class LFOSpeedCommand : ICommand 54 | { 55 | public Color Color => Color.LightSteelBlue; 56 | public string Label => "LFO Speed"; 57 | public string Arguments => Speed.ToString(); 58 | 59 | public byte Speed { get; set; } 60 | } 61 | internal class LFOTypeCommand : ICommand 62 | { 63 | public Color Color => Color.LightSteelBlue; 64 | public string Label => "LFO Type"; 65 | public string Arguments => Type.ToString(); 66 | 67 | public LFOType Type { get; set; } 68 | } 69 | internal class LibraryCommand : ICommand 70 | { 71 | public Color Color => Color.SteelBlue; 72 | public string Label => "Library Call"; 73 | public string Arguments => $"{Command}, {Argument}"; 74 | 75 | public byte Command { get; set; } 76 | public byte Argument { get; set; } 77 | } 78 | internal class MemoryAccessCommand : ICommand 79 | { 80 | public Color Color => Color.SteelBlue; 81 | public string Label => "Memory Access"; 82 | public string Arguments => $"{Operator}, {Address}, {Data}"; 83 | 84 | public byte Operator { get; set; } 85 | public byte Address { get; set; } 86 | public byte Data { get; set; } 87 | } 88 | internal class NoteCommand : ICommand 89 | { 90 | public Color Color => Color.SkyBlue; 91 | public string Label => "Note"; 92 | public string Arguments => $"{Util.Utils.GetNoteName(Key)} {Velocity} {Duration}"; 93 | 94 | public byte Key { get; set; } 95 | public byte Velocity { get; set; } 96 | public int Duration { get; set; } 97 | } 98 | internal class PanpotCommand : ICommand 99 | { 100 | public Color Color => Color.GreenYellow; 101 | public string Label => "Panpot"; 102 | public string Arguments => Panpot.ToString(); 103 | 104 | public sbyte Panpot { get; set; } 105 | } 106 | internal class PitchBendCommand : ICommand 107 | { 108 | public Color Color => Color.MediumPurple; 109 | public string Label => "Pitch Bend"; 110 | public string Arguments => Bend.ToString(); 111 | 112 | public sbyte Bend { get; set; } 113 | } 114 | internal class PitchBendRangeCommand : ICommand 115 | { 116 | public Color Color => Color.MediumPurple; 117 | public string Label => "Pitch Bend Range"; 118 | public string Arguments => Range.ToString(); 119 | 120 | public byte Range { get; set; } 121 | } 122 | internal class PriorityCommand : ICommand 123 | { 124 | public Color Color => Color.SteelBlue; 125 | public string Label => "Priority"; 126 | public string Arguments => Priority.ToString(); 127 | 128 | public byte Priority { get; set; } 129 | } 130 | internal class RepeatCommand : ICommand 131 | { 132 | public Color Color => Color.MediumSpringGreen; 133 | public string Label => "Repeat"; 134 | public string Arguments => $"{Times}, 0x{Offset:X7}"; 135 | 136 | public byte Times { get; set; } 137 | public int Offset { get; set; } 138 | } 139 | internal class RestCommand : ICommand 140 | { 141 | public Color Color => Color.PaleVioletRed; 142 | public string Label => "Rest"; 143 | public string Arguments => Rest.ToString(); 144 | 145 | public byte Rest { get; set; } 146 | } 147 | internal class ReturnCommand : ICommand 148 | { 149 | public Color Color => Color.MediumSpringGreen; 150 | public string Label => "Return"; 151 | public string Arguments => string.Empty; 152 | } 153 | internal class TempoCommand : ICommand 154 | { 155 | public Color Color => Color.DeepSkyBlue; 156 | public string Label => "Tempo"; 157 | public string Arguments => Tempo.ToString(); 158 | 159 | public ushort Tempo { get; set; } 160 | } 161 | internal class TransposeCommand : ICommand 162 | { 163 | public Color Color => Color.SkyBlue; 164 | public string Label => "Transpose"; 165 | public string Arguments => Transpose.ToString(); 166 | 167 | public sbyte Transpose { get; set; } 168 | } 169 | internal class TuneCommand : ICommand 170 | { 171 | public Color Color => Color.MediumPurple; 172 | public string Label => "Fine Tune"; 173 | public string Arguments => Tune.ToString(); 174 | 175 | public sbyte Tune { get; set; } 176 | } 177 | internal class VoiceCommand : ICommand 178 | { 179 | public Color Color => Color.DarkSalmon; 180 | public string Label => "Voice"; 181 | public string Arguments => Voice.ToString(); 182 | 183 | public byte Voice { get; set; } 184 | } 185 | internal class VolumeCommand : ICommand 186 | { 187 | public Color Color => Color.SteelBlue; 188 | public string Label => "Volume"; 189 | public string Arguments => Volume.ToString(); 190 | 191 | public byte Volume { get; set; } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /VG Music Studio/Core/GBA/MP2K/Enums.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Kermalis.VGMusicStudio.Core.GBA.MP2K 4 | { 5 | internal enum EnvelopeState : byte 6 | { 7 | Initializing, 8 | Rising, 9 | Decaying, 10 | Playing, 11 | Releasing, 12 | Dying, 13 | Dead 14 | } 15 | internal enum ReverbType : byte 16 | { 17 | None, 18 | Normal, 19 | Camelot1, 20 | Camelot2, 21 | MGAT 22 | } 23 | 24 | internal enum GoldenSunPSGType : byte 25 | { 26 | Square, 27 | Saw, 28 | Triangle 29 | } 30 | internal enum LFOType : byte 31 | { 32 | Pitch, 33 | Volume, 34 | Panpot 35 | } 36 | internal enum SquarePattern : byte 37 | { 38 | D12, 39 | D25, 40 | D50, 41 | D75 42 | } 43 | internal enum NoisePattern : byte 44 | { 45 | Fine, 46 | Rough 47 | } 48 | internal enum VoiceType : byte 49 | { 50 | PCM8, 51 | Square1, 52 | Square2, 53 | PCM4, 54 | Noise, 55 | Invalid5, 56 | Invalid6, 57 | Invalid7 58 | } 59 | [Flags] 60 | internal enum VoiceFlags : byte 61 | { 62 | // These are flags that apply to the types 63 | Fixed = 0x08, // PCM8 64 | OffWithNoise = 0x08, // Square1, Square2, PCM4, Noise 65 | Reversed = 0x10, // PCM8 66 | Compressed = 0x20, // PCM8 (Only in Pokémon main series games) 67 | 68 | // These are flags that cancel out every other bit after them if set so they should only be checked with equality 69 | KeySplit = 0x40, 70 | Drum = 0x80 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /VG Music Studio/Core/GBA/MP2K/Structs.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.EndianBinaryIO; 2 | 3 | namespace Kermalis.VGMusicStudio.Core.GBA.MP2K 4 | { 5 | internal class SongEntry 6 | { 7 | public int HeaderOffset { get; set; } 8 | public short Player { get; set; } 9 | [BinaryArrayFixedLength(2)] 10 | public byte[] Unknown { get; set; } 11 | } 12 | internal class SongHeader 13 | { 14 | public byte NumTracks { get; set; } 15 | public byte NumBlocks { get; set; } 16 | public byte Priority { get; set; } 17 | public byte Reverb { get; set; } 18 | public int VoiceTableOffset { get; set; } 19 | [BinaryArrayVariableLength(nameof(NumTracks))] 20 | public int[] TrackOffsets { get; set; } 21 | } 22 | internal class VoiceEntry 23 | { 24 | public byte Type { get; set; } // 0 25 | public byte RootKey { get; set; } // 1 26 | public byte Unknown { get; set; } // 2 27 | public byte Pan { get; set; } // 3 28 | /// SquarePattern for Square1/Square2, NoisePattern for Noise, Address for PCM8/PCM4/KeySplit/Drum 29 | public int Int4 { get; set; } // 4 30 | /// ADSR for PCM8/Square1/Square2/PCM4/Noise, KeysAddress for KeySplit 31 | public ADSR ADSR { get; set; } // 8 32 | [BinaryIgnore] 33 | public int Int8 => (ADSR.R << 24) | (ADSR.S << 16) | (ADSR.D << 8) | (ADSR.A); 34 | } 35 | internal struct ADSR // Used as a struct in GBChannel 36 | { 37 | public byte A { get; set; } 38 | public byte D { get; set; } 39 | public byte S { get; set; } 40 | public byte R { get; set; } 41 | } 42 | internal class GoldenSunPSG 43 | { 44 | /// Always 0x80 45 | public byte Unknown { get; set; } 46 | public GoldenSunPSGType Type { get; set; } 47 | public byte InitialCycle { get; set; } 48 | public byte CycleSpeed { get; set; } 49 | public byte CycleAmplitude { get; set; } 50 | public byte MinimumCycle { get; set; } 51 | } 52 | internal class SampleHeader 53 | { 54 | /// 0x40000000 if True 55 | public int DoesLoop { get; set; } 56 | /// Right shift 10 for value 57 | public int SampleRate { get; set; } 58 | public int LoopOffset { get; set; } 59 | public int Length { get; set; } 60 | } 61 | 62 | internal struct ChannelVolume 63 | { 64 | public float LeftVol, RightVol; 65 | } 66 | internal struct Note 67 | { 68 | public byte Key, OriginalKey; 69 | public byte Velocity; 70 | /// -1 if forever 71 | public int Duration; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /VG Music Studio/Core/GBA/MP2K/Track.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Kermalis.VGMusicStudio.Core.GBA.MP2K 4 | { 5 | internal class Track 6 | { 7 | public readonly byte Index; 8 | private readonly int _startOffset; 9 | public byte Voice; 10 | public byte PitchBendRange; 11 | public byte Priority; 12 | public byte Volume; 13 | public byte Rest; 14 | public byte LFOPhase; 15 | public byte LFODelayCount; 16 | public byte LFOSpeed; 17 | public byte LFODelay; 18 | public byte LFODepth; 19 | public LFOType LFOType; 20 | public sbyte PitchBend; 21 | public sbyte Tune; 22 | public sbyte Panpot; 23 | public sbyte Transpose; 24 | public bool Ready; 25 | public bool Stopped; 26 | public int DataOffset; 27 | public int[] CallStack = new int[3]; 28 | public byte CallStackDepth; 29 | public byte RunCmd; 30 | public byte PrevKey; 31 | public byte PrevVelocity; 32 | 33 | public readonly List Channels = new List(); 34 | 35 | public int GetPitch() 36 | { 37 | int lfo = LFOType == LFOType.Pitch ? (Utils.Tri(LFOPhase) * LFODepth) >> 8 : 0; 38 | return (PitchBend * PitchBendRange) + Tune + lfo; 39 | } 40 | public byte GetVolume() 41 | { 42 | int lfo = LFOType == LFOType.Volume ? (Utils.Tri(LFOPhase) * LFODepth * 3 * Volume) >> 19 : 0; 43 | int v = Volume + lfo; 44 | if (v < 0) 45 | { 46 | v = 0; 47 | } 48 | else if (v > 0x7F) 49 | { 50 | v = 0x7F; 51 | } 52 | return (byte)v; 53 | } 54 | public sbyte GetPanpot() 55 | { 56 | int lfo = LFOType == LFOType.Panpot ? (Utils.Tri(LFOPhase) * LFODepth * 3) >> 12 : 0; 57 | int p = Panpot + lfo; 58 | if (p < -0x40) 59 | { 60 | p = -0x40; 61 | } 62 | else if (p > 0x3F) 63 | { 64 | p = 0x3F; 65 | } 66 | return (sbyte)p; 67 | } 68 | 69 | public Track(byte i, int startOffset) 70 | { 71 | Index = i; 72 | _startOffset = startOffset; 73 | } 74 | public void Init() 75 | { 76 | Voice = 0; 77 | Priority = 0; 78 | Rest = 0; 79 | LFODelay = 0; 80 | LFODelayCount = 0; 81 | LFOPhase = 0; 82 | LFODepth = 0; 83 | CallStackDepth = 0; 84 | PitchBend = 0; 85 | Tune = 0; 86 | Panpot = 0; 87 | Transpose = 0; 88 | DataOffset = _startOffset; 89 | RunCmd = 0; 90 | PrevKey = 0; 91 | PrevVelocity = 0x7F; 92 | PitchBendRange = 2; 93 | LFOType = LFOType.Pitch; 94 | Ready = false; 95 | Stopped = false; 96 | LFOSpeed = 22; 97 | Volume = 100; 98 | StopAllChannels(); 99 | } 100 | public void Tick() 101 | { 102 | if (Rest != 0) 103 | { 104 | Rest--; 105 | } 106 | if (LFODepth > 0) 107 | { 108 | LFOPhase += LFOSpeed; 109 | } 110 | else 111 | { 112 | LFOPhase = 0; 113 | } 114 | int active = 0; 115 | Channel[] chans = Channels.ToArray(); 116 | for (int i = 0; i < chans.Length; i++) 117 | { 118 | if (chans[i].TickNote()) 119 | { 120 | active++; 121 | } 122 | } 123 | if (active != 0) 124 | { 125 | if (LFODelayCount > 0) 126 | { 127 | LFODelayCount--; 128 | LFOPhase = 0; 129 | } 130 | } 131 | else 132 | { 133 | LFODelayCount = LFODelay; 134 | } 135 | if ((LFODelay == LFODelayCount && LFODelay != 0) || LFOSpeed == 0) 136 | { 137 | LFOPhase = 0; 138 | } 139 | } 140 | 141 | public void ReleaseChannels(int key) 142 | { 143 | Channel[] chans = Channels.ToArray(); 144 | for (int i = 0; i < chans.Length; i++) 145 | { 146 | Channel c = chans[i]; 147 | if (c.Note.OriginalKey == key && c.Note.Duration == -1) 148 | { 149 | c.Release(); 150 | } 151 | } 152 | } 153 | public void StopAllChannels() 154 | { 155 | Channel[] chans = Channels.ToArray(); 156 | for (int i = 0; i < chans.Length; i++) 157 | { 158 | chans[i].Stop(); 159 | } 160 | } 161 | public void UpdateChannels() 162 | { 163 | byte vol = GetVolume(); 164 | sbyte pan = GetPanpot(); 165 | int pitch = GetPitch(); 166 | for (int i = 0; i < Channels.Count; i++) 167 | { 168 | Channel c = Channels[i]; 169 | c.SetVolume(vol, pan); 170 | c.SetPitch(pitch); 171 | } 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /VG Music Studio/Core/GBA/MP2K/Utils.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace Kermalis.VGMusicStudio.Core.GBA.MP2K 5 | { 6 | internal static class Utils 7 | { 8 | public static readonly byte[] RestTable = new byte[49] 9 | { 10 | 00, 01, 02, 03, 04, 05, 06, 07, 11 | 08, 09, 10, 11, 12, 13, 14, 15, 12 | 16, 17, 18, 19, 20, 21, 22, 23, 13 | 24, 28, 30, 32, 36, 40, 42, 44, 14 | 48, 52, 54, 56, 60, 64, 66, 68, 15 | 72, 76, 78, 80, 84, 88, 90, 92, 16 | 96 17 | }; 18 | public static readonly (int sampleRate, int samplesPerBuffer)[] FrequencyTable = new (int, int)[12] 19 | { 20 | (05734, 096), // 59.72916666666667 21 | (07884, 132), // 59.72727272727273 22 | (10512, 176), // 59.72727272727273 23 | (13379, 224), // 59.72767857142857 24 | (15768, 264), // 59.72727272727273 25 | (18157, 304), // 59.72697368421053 26 | (21024, 352), // 59.72727272727273 27 | (26758, 448), // 59.72767857142857 28 | (31536, 528), // 59.72727272727273 29 | (36314, 608), // 59.72697368421053 30 | (40137, 672), // 59.72767857142857 31 | (42048, 704) // 59.72727272727273 32 | }; 33 | 34 | // Squares 35 | public static readonly float[] SquareD12 = new float[8] { 0.875f, -0.125f, -0.125f, -0.125f, -0.125f, -0.125f, -0.125f, -0.125f }; 36 | public static readonly float[] SquareD25 = new float[8] { 0.750f, 0.750f, -0.250f, -0.250f, -0.250f, -0.250f, -0.250f, -0.250f }; 37 | public static readonly float[] SquareD50 = new float[8] { 0.500f, 0.500f, 0.500f, 0.500f, -0.500f, -0.500f, -0.500f, -0.500f }; 38 | public static readonly float[] SquareD75 = new float[8] { 0.250f, 0.250f, 0.250f, 0.250f, 0.250f, 0.250f, -0.750f, -0.750f }; 39 | 40 | // Noises 41 | public static readonly BitArray NoiseFine; 42 | public static readonly BitArray NoiseRough; 43 | public static readonly byte[] NoiseFrequencyTable = new byte[60] 44 | { 45 | 0xD7, 0xD6, 0xD5, 0xD4, 46 | 0xC7, 0xC6, 0xC5, 0xC4, 47 | 0xB7, 0xB6, 0xB5, 0xB4, 48 | 0xA7, 0xA6, 0xA5, 0xA4, 49 | 0x97, 0x96, 0x95, 0x94, 50 | 0x87, 0x86, 0x85, 0x84, 51 | 0x77, 0x76, 0x75, 0x74, 52 | 0x67, 0x66, 0x65, 0x64, 53 | 0x57, 0x56, 0x55, 0x54, 54 | 0x47, 0x46, 0x45, 0x44, 55 | 0x37, 0x36, 0x35, 0x34, 56 | 0x27, 0x26, 0x25, 0x24, 57 | 0x17, 0x16, 0x15, 0x14, 58 | 0x07, 0x06, 0x05, 0x04, 59 | 0x03, 0x02, 0x01, 0x00 60 | }; 61 | 62 | // PCM4 63 | public static float[] PCM4ToFloat(int sampleOffset) 64 | { 65 | var config = (Config)Engine.Instance.Config; 66 | float[] sample = new float[0x20]; 67 | float sum = 0; 68 | for (int i = 0; i < 0x10; i++) 69 | { 70 | byte b = config.ROM[sampleOffset + i]; 71 | float first = (b >> 4) / 16f; 72 | float second = (b & 0xF) / 16f; 73 | sum += sample[i * 2] = first; 74 | sum += sample[(i * 2) + 1] = second; 75 | } 76 | float dcCorrection = sum / 0x20; 77 | for (int i = 0; i < 0x20; i++) 78 | { 79 | sample[i] -= dcCorrection; 80 | } 81 | return sample; 82 | } 83 | 84 | // Pokémon Only 85 | private static readonly sbyte[] _compressionLookup = new sbyte[16] 86 | { 87 | 0, 1, 4, 9, 16, 25, 36, 49, -64, -49, -36, -25, -16, -9, -4, -1 88 | }; 89 | public static sbyte[] Decompress(int sampleOffset, int sampleLength) 90 | { 91 | var config = (Config)Engine.Instance.Config; 92 | var samples = new List(); 93 | sbyte compressionLevel = 0; 94 | int compressionByte = 0, compressionIdx = 0; 95 | 96 | for (int i = 0; true; i++) 97 | { 98 | byte b = config.ROM[sampleOffset + i]; 99 | if (compressionByte == 0) 100 | { 101 | compressionByte = 0x20; 102 | compressionLevel = (sbyte)b; 103 | samples.Add(compressionLevel); 104 | if (++compressionIdx >= sampleLength) 105 | { 106 | break; 107 | } 108 | } 109 | else 110 | { 111 | if (compressionByte < 0x20) 112 | { 113 | compressionLevel += _compressionLookup[b >> 4]; 114 | samples.Add(compressionLevel); 115 | if (++compressionIdx >= sampleLength) 116 | { 117 | break; 118 | } 119 | } 120 | compressionByte--; 121 | compressionLevel += _compressionLookup[b & 0xF]; 122 | samples.Add(compressionLevel); 123 | if (++compressionIdx >= sampleLength) 124 | { 125 | break; 126 | } 127 | } 128 | } 129 | 130 | return samples.ToArray(); 131 | } 132 | 133 | static Utils() 134 | { 135 | NoiseFine = new BitArray(0x8000); 136 | int reg = 0x4000; 137 | for (int i = 0; i < NoiseFine.Length; i++) 138 | { 139 | if ((reg & 1) == 1) 140 | { 141 | reg >>= 1; 142 | reg ^= 0x6000; 143 | NoiseFine[i] = true; 144 | } 145 | else 146 | { 147 | reg >>= 1; 148 | NoiseFine[i] = false; 149 | } 150 | } 151 | NoiseRough = new BitArray(0x80); 152 | reg = 0x40; 153 | for (int i = 0; i < NoiseRough.Length; i++) 154 | { 155 | if ((reg & 1) == 1) 156 | { 157 | reg >>= 1; 158 | reg ^= 0x60; 159 | NoiseRough[i] = true; 160 | } 161 | else 162 | { 163 | reg >>= 1; 164 | NoiseRough[i] = false; 165 | } 166 | } 167 | } 168 | public static int Tri(int index) 169 | { 170 | index = (index - 64) & 0xFF; 171 | return (index < 128) ? (index * 12) - 768 : 2304 - (index * 12); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /VG Music Studio/Core/GBA/Utils.cs: -------------------------------------------------------------------------------- 1 | namespace Kermalis.VGMusicStudio.Core.GBA 2 | { 3 | internal static class Utils 4 | { 5 | public const double AGB_FPS = 59.7275; 6 | public const int SystemClock = 16777216; // 16.777216 MHz (16*1024*1024 Hz) 7 | 8 | public const int CartridgeOffset = 0x08000000; 9 | public const int CartridgeCapacity = 0x02000000; 10 | 11 | public static readonly string[] PSGTypes = new string[4] { "Square 1", "Square 2", "PCM4", "Noise" }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /VG Music Studio/Core/GlobalConfig.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.VGMusicStudio.Properties; 2 | using Kermalis.VGMusicStudio.Util; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using YamlDotNet.RepresentationModel; 7 | 8 | namespace Kermalis.VGMusicStudio.Core 9 | { 10 | internal enum PlaylistMode : byte 11 | { 12 | Random, 13 | Sequential 14 | } 15 | 16 | internal sealed class GlobalConfig 17 | { 18 | public static GlobalConfig Instance { get; private set; } 19 | 20 | public readonly bool TaskbarProgress; 21 | public readonly ushort RefreshRate; 22 | public readonly bool CenterIndicators; 23 | public readonly bool PanpotIndicators; 24 | public readonly PlaylistMode PlaylistMode; 25 | public readonly long PlaylistSongLoops; 26 | public readonly long PlaylistFadeOutMilliseconds; 27 | public readonly sbyte MiddleCOctave; 28 | public readonly HSLColor[] Colors; 29 | 30 | private GlobalConfig() 31 | { 32 | const string configFile = "Config.yaml"; 33 | using (StreamReader fileStream = File.OpenText(Utils.CombineWithBaseDirectory(configFile))) 34 | { 35 | try 36 | { 37 | var yaml = new YamlStream(); 38 | yaml.Load(fileStream); 39 | 40 | var mapping = (YamlMappingNode)yaml.Documents[0].RootNode; 41 | TaskbarProgress = mapping.GetValidBoolean(nameof(TaskbarProgress)); 42 | RefreshRate = (ushort)mapping.GetValidValue(nameof(RefreshRate), 1, 1000); 43 | CenterIndicators = mapping.GetValidBoolean(nameof(CenterIndicators)); 44 | PanpotIndicators = mapping.GetValidBoolean(nameof(PanpotIndicators)); 45 | PlaylistMode = mapping.GetValidEnum(nameof(PlaylistMode)); 46 | PlaylistSongLoops = mapping.GetValidValue(nameof(PlaylistSongLoops), 0, long.MaxValue); 47 | PlaylistFadeOutMilliseconds = mapping.GetValidValue(nameof(PlaylistFadeOutMilliseconds), 0, long.MaxValue); 48 | MiddleCOctave = (sbyte)mapping.GetValidValue(nameof(MiddleCOctave), sbyte.MinValue, sbyte.MaxValue); 49 | 50 | var cmap = (YamlMappingNode)mapping.Children[nameof(Colors)]; 51 | Colors = new HSLColor[256]; 52 | foreach (KeyValuePair c in cmap) 53 | { 54 | int i = (int)Utils.ParseValue(string.Format(Strings.ConfigKeySubkey, nameof(Colors)), c.Key.ToString(), 0, 127); 55 | if (Colors[i] != null) 56 | { 57 | throw new Exception(string.Format(Strings.ErrorParseConfig, configFile, Environment.NewLine + string.Format(Strings.ErrorConfigColorRepeated, i))); 58 | } 59 | double h = 0, s = 0, l = 0; 60 | foreach (KeyValuePair v in ((YamlMappingNode)c.Value).Children) 61 | { 62 | string key = v.Key.ToString(); 63 | string valueName = string.Format(Strings.ConfigKeySubkey, string.Format("{0} {1}", nameof(Colors), i)); 64 | if (key == "H") 65 | { 66 | h = Utils.ParseValue(valueName, v.Value.ToString(), 0, 240); 67 | } 68 | else if (key == "S") 69 | { 70 | s = Utils.ParseValue(valueName, v.Value.ToString(), 0, 240); 71 | } 72 | else if (key == "L") 73 | { 74 | l = Utils.ParseValue(valueName, v.Value.ToString(), 0, 240); 75 | } 76 | else 77 | { 78 | throw new Exception(string.Format(Strings.ErrorParseConfig, configFile, Environment.NewLine + string.Format(Strings.ErrorConfigColorInvalidKey, i))); 79 | } 80 | } 81 | var co = new HSLColor(h, s, l); 82 | Colors[i] = co; 83 | Colors[i + 128] = co; 84 | } 85 | for (int i = 0; i < Colors.Length; i++) 86 | { 87 | if (Colors[i] == null) 88 | { 89 | throw new Exception(string.Format(Strings.ErrorParseConfig, configFile, Environment.NewLine + string.Format(Strings.ErrorConfigColorMissing, i))); 90 | } 91 | } 92 | } 93 | catch (BetterKeyNotFoundException ex) 94 | { 95 | throw new Exception(string.Format(Strings.ErrorParseConfig, configFile, Environment.NewLine + string.Format(Strings.ErrorConfigKeyMissing, ex.Key))); 96 | } 97 | catch (Exception ex) when (ex is InvalidValueException || ex is YamlDotNet.Core.YamlException) 98 | { 99 | throw new Exception(string.Format(Strings.ErrorParseConfig, configFile, Environment.NewLine + ex.Message)); 100 | } 101 | } 102 | } 103 | 104 | public static void Init() 105 | { 106 | Instance = new GlobalConfig(); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /VG Music Studio/Core/Mixer.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.VGMusicStudio.UI; 2 | using NAudio.CoreAudioApi; 3 | using NAudio.CoreAudioApi.Interfaces; 4 | using NAudio.Wave; 5 | using System; 6 | 7 | namespace Kermalis.VGMusicStudio.Core 8 | { 9 | internal abstract class Mixer : IAudioSessionEventsHandler, IDisposable 10 | { 11 | public readonly bool[] Mutes = new bool[SongInfoControl.SongInfo.MaxTracks]; 12 | private IWavePlayer _out; 13 | private AudioSessionControl _appVolume; 14 | 15 | protected void Init(IWaveProvider waveProvider) 16 | { 17 | _out = new WasapiOut(); 18 | _out.Init(waveProvider); 19 | using (var en = new MMDeviceEnumerator()) 20 | { 21 | SessionCollection sessions = en.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia).AudioSessionManager.Sessions; 22 | int id = System.Diagnostics.Process.GetCurrentProcess().Id; 23 | for (int i = 0; i < sessions.Count; i++) 24 | { 25 | AudioSessionControl session = sessions[i]; 26 | if (session.GetProcessID == id) 27 | { 28 | _appVolume = session; 29 | _appVolume.RegisterEventClient(this); 30 | break; 31 | } 32 | } 33 | } 34 | _out.Play(); 35 | } 36 | 37 | private bool _volChange = true; 38 | public void OnVolumeChanged(float volume, bool isMuted) 39 | { 40 | if (_volChange) 41 | { 42 | MainForm.Instance.SetVolumeBarValue(volume); 43 | } 44 | _volChange = true; 45 | } 46 | public void OnDisplayNameChanged(string displayName) 47 | { 48 | throw new NotImplementedException(); 49 | } 50 | public void OnIconPathChanged(string iconPath) 51 | { 52 | throw new NotImplementedException(); 53 | } 54 | public void OnChannelVolumeChanged(uint channelCount, IntPtr newVolumes, uint channelIndex) 55 | { 56 | throw new NotImplementedException(); 57 | } 58 | public void OnGroupingParamChanged(ref Guid groupingId) 59 | { 60 | throw new NotImplementedException(); 61 | } 62 | // Fires on @out.Play() and @out.Stop() 63 | public void OnStateChanged(AudioSessionState state) 64 | { 65 | if (state == AudioSessionState.AudioSessionStateActive) 66 | { 67 | OnVolumeChanged(_appVolume.SimpleAudioVolume.Volume, _appVolume.SimpleAudioVolume.Mute); 68 | } 69 | } 70 | public void OnSessionDisconnected(AudioSessionDisconnectReason disconnectReason) 71 | { 72 | throw new NotImplementedException(); 73 | } 74 | public void SetVolume(float volume) 75 | { 76 | _volChange = false; 77 | _appVolume.SimpleAudioVolume.Volume = volume; 78 | } 79 | 80 | public virtual void Dispose() 81 | { 82 | _out.Stop(); 83 | _out.Dispose(); 84 | _appVolume.Dispose(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /VG Music Studio/Core/NDS/DSE/Commands.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Linq; 3 | 4 | namespace Kermalis.VGMusicStudio.Core.NDS.DSE 5 | { 6 | internal class ExpressionCommand : ICommand 7 | { 8 | public Color Color => Color.SteelBlue; 9 | public string Label => "Expression"; 10 | public string Arguments => Expression.ToString(); 11 | 12 | public byte Expression { get; set; } 13 | } 14 | internal class FinishCommand : ICommand 15 | { 16 | public Color Color => Color.MediumSpringGreen; 17 | public string Label => "Finish"; 18 | public string Arguments => string.Empty; 19 | } 20 | internal class InvalidCommand : ICommand 21 | { 22 | public Color Color => Color.MediumVioletRed; 23 | public string Label => $"Invalid 0x{Command:X}"; 24 | public string Arguments => string.Empty; 25 | 26 | public byte Command { get; set; } 27 | } 28 | internal class LoopStartCommand : ICommand 29 | { 30 | public Color Color => Color.MediumSpringGreen; 31 | public string Label => "Loop Start"; 32 | public string Arguments => $"0x{Offset:X}"; 33 | 34 | public long Offset { get; set; } 35 | } 36 | internal class NoteCommand : ICommand 37 | { 38 | public Color Color => Color.SkyBlue; 39 | public string Label => "Note"; 40 | public string Arguments => $"{Util.Utils.GetPianoKeyName(Key)} {OctaveChange} {Velocity} {Duration}"; 41 | 42 | public byte Key { get; set; } 43 | public sbyte OctaveChange { get; set; } 44 | public byte Velocity { get; set; } 45 | public uint Duration { get; set; } 46 | } 47 | internal class OctaveAddCommand : ICommand 48 | { 49 | public Color Color => Color.SkyBlue; 50 | public string Label => "Add To Octave"; 51 | public string Arguments => OctaveChange.ToString(); 52 | 53 | public sbyte OctaveChange { get; set; } 54 | } 55 | internal class OctaveSetCommand : ICommand 56 | { 57 | public Color Color => Color.SkyBlue; 58 | public string Label => "Set Octave"; 59 | public string Arguments => Octave.ToString(); 60 | 61 | public byte Octave { get; set; } 62 | } 63 | internal class PanpotCommand : ICommand 64 | { 65 | public Color Color => Color.GreenYellow; 66 | public string Label => "Panpot"; 67 | public string Arguments => Panpot.ToString(); 68 | 69 | public sbyte Panpot { get; set; } 70 | } 71 | internal class PitchBendCommand : ICommand 72 | { 73 | public Color Color => Color.MediumPurple; 74 | public string Label => "Pitch Bend"; 75 | public string Arguments => $"{(sbyte)Bend}, {(sbyte)(Bend >> 8)}"; 76 | 77 | public ushort Bend { get; set; } 78 | } 79 | internal class RestCommand : ICommand 80 | { 81 | public Color Color => Color.PaleVioletRed; 82 | public string Label => "Rest"; 83 | public string Arguments => Rest.ToString(); 84 | 85 | public uint Rest { get; set; } 86 | } 87 | internal class SkipBytesCommand : ICommand 88 | { 89 | public Color Color => Color.MediumVioletRed; 90 | public string Label => $"Skip 0x{Command:X}"; 91 | public string Arguments => string.Join(", ", SkippedBytes.Select(b => $"0x{b:X}")); 92 | 93 | public byte Command { get; set; } 94 | public byte[] SkippedBytes { get; set; } 95 | } 96 | internal class TempoCommand : ICommand 97 | { 98 | public Color Color => Color.DeepSkyBlue; 99 | public string Label => $"Tempo {Command - 0xA3}"; // The two possible tempo commands are 0xA4 and 0xA5 100 | public string Arguments => Tempo.ToString(); 101 | 102 | public byte Command { get; set; } 103 | public byte Tempo { get; set; } 104 | } 105 | internal class UnknownCommand : ICommand 106 | { 107 | public Color Color => Color.MediumVioletRed; 108 | public string Label => $"Unknown 0x{Command:X}"; 109 | public string Arguments => string.Join(", ", Args.Select(b => $"0x{b:X}")); 110 | 111 | public byte Command { get; set; } 112 | public byte[] Args { get; set; } 113 | } 114 | internal class VoiceCommand : ICommand 115 | { 116 | public Color Color => Color.DarkSalmon; 117 | public string Label => "Voice"; 118 | public string Arguments => Voice.ToString(); 119 | 120 | public byte Voice { get; set; } 121 | } 122 | internal class VolumeCommand : ICommand 123 | { 124 | public Color Color => Color.SteelBlue; 125 | public string Label => "Volume"; 126 | public string Arguments => Volume.ToString(); 127 | 128 | public byte Volume { get; set; } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /VG Music Studio/Core/NDS/DSE/Config.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.EndianBinaryIO; 2 | using Kermalis.VGMusicStudio.Properties; 3 | using System; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | namespace Kermalis.VGMusicStudio.Core.NDS.DSE 8 | { 9 | internal class Config : Core.Config 10 | { 11 | public readonly string BGMPath; 12 | public readonly string[] BGMFiles; 13 | 14 | public Config(string bgmPath) 15 | { 16 | BGMPath = bgmPath; 17 | BGMFiles = Directory.GetFiles(bgmPath, "bgm*.smd", SearchOption.TopDirectoryOnly); 18 | if (BGMFiles.Length == 0) 19 | { 20 | throw new Exception(Strings.ErrorDSENoSequences); 21 | } 22 | var songs = new Song[BGMFiles.Length]; 23 | for (int i = 0; i < BGMFiles.Length; i++) 24 | { 25 | using (var reader = new EndianBinaryReader(File.OpenRead(BGMFiles[i]))) 26 | { 27 | SMD.Header header = reader.ReadObject(); 28 | songs[i] = new Song(i, $"{Path.GetFileNameWithoutExtension(BGMFiles[i])} - {new string(header.Label.TakeWhile(c => c != '\0').ToArray())}"); 29 | } 30 | } 31 | Playlists.Add(new Playlist(Strings.PlaylistMusic, songs)); 32 | } 33 | 34 | public override string GetGameName() 35 | { 36 | return "DSE"; 37 | } 38 | public override string GetSongName(long index) 39 | { 40 | return index < 0 || index >= BGMFiles.Length 41 | ? index.ToString() 42 | : '\"' + BGMFiles[index] + '\"'; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /VG Music Studio/Core/NDS/DSE/Enums.cs: -------------------------------------------------------------------------------- 1 | namespace Kermalis.VGMusicStudio.Core.NDS.DSE 2 | { 3 | internal enum EnvelopeState : byte 4 | { 5 | Zero = 0, 6 | One = 1, 7 | Two = 2, 8 | Hold = 3, 9 | Decay = 4, 10 | Decay2 = 5, 11 | Six = 6, 12 | Seven = 7, 13 | Eight = 8 14 | } 15 | 16 | internal enum SampleFormat : ushort 17 | { 18 | PCM8 = 0x000, 19 | PCM16 = 0x100, 20 | ADPCM = 0x200 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /VG Music Studio/Core/NDS/DSE/Mixer.cs: -------------------------------------------------------------------------------- 1 | using NAudio.Wave; 2 | using System; 3 | 4 | namespace Kermalis.VGMusicStudio.Core.NDS.DSE 5 | { 6 | internal class Mixer : Core.Mixer 7 | { 8 | private const int _numChannels = 0x20; 9 | private readonly float _samplesReciprocal; 10 | private readonly int _samplesPerBuffer; 11 | private bool _isFading; 12 | private long _fadeMicroFramesLeft; 13 | private float _fadePos; 14 | private float _fadeStepPerMicroframe; 15 | 16 | private readonly Channel[] _channels; 17 | private readonly BufferedWaveProvider _buffer; 18 | 19 | public Mixer() 20 | { 21 | // The sampling frequency of the mixer is 1.04876 MHz with an amplitude resolution of 24 bits, but the sampling frequency after mixing with PWM modulation is 32.768 kHz with an amplitude resolution of 10 bits. 22 | // - gbatek 23 | // I'm not using either of those because the samples per buffer leads to an overflow eventually 24 | const int sampleRate = 65456; 25 | _samplesPerBuffer = 341; // TODO 26 | _samplesReciprocal = 1f / _samplesPerBuffer; 27 | 28 | _channels = new Channel[_numChannels]; 29 | for (byte i = 0; i < _numChannels; i++) 30 | { 31 | _channels[i] = new Channel(i); 32 | } 33 | 34 | _buffer = new BufferedWaveProvider(new WaveFormat(sampleRate, 16, 2)) 35 | { 36 | DiscardOnBufferOverflow = true, 37 | BufferLength = _samplesPerBuffer * 64 38 | }; 39 | Init(_buffer); 40 | } 41 | public override void Dispose() 42 | { 43 | base.Dispose(); 44 | CloseWaveWriter(); 45 | } 46 | 47 | public Channel AllocateChannel() 48 | { 49 | int GetScore(Channel c) 50 | { 51 | // Free channels should be used before releasing channels 52 | return c.Owner == null ? -2 : Utils.IsStateRemovable(c.State) ? -1 : 0; 53 | } 54 | Channel nChan = null; 55 | for (int i = 0; i < _numChannels; i++) 56 | { 57 | Channel c = _channels[i]; 58 | if (nChan != null) 59 | { 60 | int nScore = GetScore(nChan); 61 | int cScore = GetScore(c); 62 | if (cScore <= nScore && (cScore < nScore || c.Volume <= nChan.Volume)) 63 | { 64 | nChan = c; 65 | } 66 | } 67 | else 68 | { 69 | nChan = c; 70 | } 71 | } 72 | return nChan != null && 0 >= GetScore(nChan) ? nChan : null; 73 | } 74 | 75 | public void ChannelTick() 76 | { 77 | for (int i = 0; i < _numChannels; i++) 78 | { 79 | Channel chan = _channels[i]; 80 | if (chan.Owner != null) 81 | { 82 | chan.Volume = (byte)chan.StepEnvelope(); 83 | if (chan.NoteLength == 0 && !Utils.IsStateRemovable(chan.State)) 84 | { 85 | chan.SetEnvelopePhase7_2074ED8(); 86 | } 87 | int vol = SDAT.Utils.SustainTable[chan.NoteVelocity] + SDAT.Utils.SustainTable[chan.Volume] + SDAT.Utils.SustainTable[chan.Owner.Volume] + SDAT.Utils.SustainTable[chan.Owner.Expression]; 88 | //int pitch = ((chan.Key - chan.BaseKey) << 6) + chan.SweepMain() + chan.Owner.GetPitch(); // "<< 6" is "* 0x40" 89 | int pitch = (chan.Key - chan.RootKey) << 6; // "<< 6" is "* 0x40" 90 | if (Utils.IsStateRemovable(chan.State) && vol <= -92544) 91 | { 92 | chan.Stop(); 93 | } 94 | else 95 | { 96 | chan.Volume = SDAT.Utils.GetChannelVolume(vol); 97 | chan.Panpot = chan.Owner.Panpot; 98 | chan.Timer = SDAT.Utils.GetChannelTimer(chan.BaseTimer, pitch); 99 | } 100 | } 101 | } 102 | } 103 | 104 | public void BeginFadeIn() 105 | { 106 | _fadePos = 0f; 107 | _fadeMicroFramesLeft = (long)(GlobalConfig.Instance.PlaylistFadeOutMilliseconds / 1000.0 * 192); 108 | _fadeStepPerMicroframe = 1f / _fadeMicroFramesLeft; 109 | _isFading = true; 110 | } 111 | public void BeginFadeOut() 112 | { 113 | _fadePos = 1f; 114 | _fadeMicroFramesLeft = (long)(GlobalConfig.Instance.PlaylistFadeOutMilliseconds / 1000.0 * 192); 115 | _fadeStepPerMicroframe = -1f / _fadeMicroFramesLeft; 116 | _isFading = true; 117 | } 118 | public bool IsFading() 119 | { 120 | return _isFading; 121 | } 122 | public bool IsFadeDone() 123 | { 124 | return _isFading && _fadeMicroFramesLeft == 0; 125 | } 126 | public void ResetFade() 127 | { 128 | _isFading = false; 129 | _fadeMicroFramesLeft = 0; 130 | } 131 | 132 | private WaveFileWriter _waveWriter; 133 | public void CreateWaveWriter(string fileName) 134 | { 135 | _waveWriter = new WaveFileWriter(fileName, _buffer.WaveFormat); 136 | } 137 | public void CloseWaveWriter() 138 | { 139 | _waveWriter?.Dispose(); 140 | } 141 | public void Process(bool output, bool recording) 142 | { 143 | float masterStep; 144 | float masterLevel; 145 | if (_isFading && _fadeMicroFramesLeft == 0) 146 | { 147 | masterStep = 0; 148 | masterLevel = 0; 149 | } 150 | else 151 | { 152 | float fromMaster = 1f; 153 | float toMaster = 1f; 154 | if (_fadeMicroFramesLeft > 0) 155 | { 156 | const float scale = 10f / 6f; 157 | fromMaster *= (_fadePos < 0f) ? 0f : (float)Math.Pow(_fadePos, scale); 158 | _fadePos += _fadeStepPerMicroframe; 159 | toMaster *= (_fadePos < 0f) ? 0f : (float)Math.Pow(_fadePos, scale); 160 | _fadeMicroFramesLeft--; 161 | } 162 | masterStep = (toMaster - fromMaster) * _samplesReciprocal; 163 | masterLevel = fromMaster; 164 | } 165 | byte[] b = new byte[4]; 166 | for (int i = 0; i < _samplesPerBuffer; i++) 167 | { 168 | int left = 0, 169 | right = 0; 170 | for (int j = 0; j < _numChannels; j++) 171 | { 172 | Channel chan = _channels[j]; 173 | if (chan.Owner != null) 174 | { 175 | bool muted = Mutes[chan.Owner.Index]; // Get mute first because chan.Process() can call chan.Stop() which sets chan.Owner to null 176 | chan.Process(out short channelLeft, out short channelRight); 177 | if (!muted) 178 | { 179 | left += channelLeft; 180 | right += channelRight; 181 | } 182 | } 183 | } 184 | float f = left * masterLevel; 185 | if (f < short.MinValue) 186 | { 187 | f = short.MinValue; 188 | } 189 | else if (f > short.MaxValue) 190 | { 191 | f = short.MaxValue; 192 | } 193 | left = (int)f; 194 | b[0] = (byte)left; 195 | b[1] = (byte)(left >> 8); 196 | f = right * masterLevel; 197 | if (f < short.MinValue) 198 | { 199 | f = short.MinValue; 200 | } 201 | else if (f > short.MaxValue) 202 | { 203 | f = short.MaxValue; 204 | } 205 | right = (int)f; 206 | b[2] = (byte)right; 207 | b[3] = (byte)(right >> 8); 208 | masterLevel += masterStep; 209 | if (output) 210 | { 211 | _buffer.AddSamples(b, 0, 4); 212 | } 213 | if (recording) 214 | { 215 | _waveWriter.Write(b, 0, 4); 216 | } 217 | } 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /VG Music Studio/Core/NDS/DSE/SMD.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.EndianBinaryIO; 2 | 3 | namespace Kermalis.VGMusicStudio.Core.NDS.DSE 4 | { 5 | internal class SMD 6 | { 7 | public class Header 8 | { 9 | [BinaryStringFixedLength(4)] 10 | public string Type { get; set; } // "smdb" or "smdl" 11 | [BinaryArrayFixedLength(4)] 12 | public byte[] Unknown1 { get; set; } 13 | public uint Length { get; set; } 14 | public ushort Version { get; set; } 15 | [BinaryArrayFixedLength(10)] 16 | public byte[] Unknown2 { get; set; } 17 | public ushort Year { get; set; } 18 | public byte Month { get; set; } 19 | public byte Day { get; set; } 20 | public byte Hour { get; set; } 21 | public byte Minute { get; set; } 22 | public byte Second { get; set; } 23 | public byte Centisecond { get; set; } 24 | [BinaryStringFixedLength(16)] 25 | public string Label { get; set; } 26 | [BinaryArrayFixedLength(16)] 27 | public byte[] Unknown3 { get; set; } 28 | } 29 | 30 | public interface ISongChunk 31 | { 32 | byte NumTracks { get; } 33 | } 34 | public class SongChunk_V402 : ISongChunk 35 | { 36 | [BinaryStringFixedLength(4)] 37 | public string Type { get; set; } 38 | [BinaryArrayFixedLength(16)] 39 | public byte[] Unknown1 { get; set; } 40 | public byte NumTracks { get; set; } 41 | public byte NumChannels { get; set; } 42 | [BinaryArrayFixedLength(4)] 43 | public byte[] Unknown2 { get; set; } 44 | public sbyte MasterVolume { get; set; } 45 | public sbyte MasterPanpot { get; set; } 46 | [BinaryArrayFixedLength(4)] 47 | public byte[] Unknown3 { get; set; } 48 | } 49 | public class SongChunk_V415 : ISongChunk 50 | { 51 | [BinaryStringFixedLength(4)] 52 | public string Type { get; set; } 53 | [BinaryArrayFixedLength(18)] 54 | public byte[] Unknown1 { get; set; } 55 | public byte NumTracks { get; set; } 56 | public byte NumChannels { get; set; } 57 | [BinaryArrayFixedLength(40)] 58 | public byte[] Unknown2 { get; set; } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /VG Music Studio/Core/NDS/DSE/Track.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Kermalis.VGMusicStudio.Core.NDS.DSE 4 | { 5 | internal class Track 6 | { 7 | public readonly byte Index; 8 | private readonly long _startOffset; 9 | public byte Octave; 10 | public byte Voice; 11 | public byte Expression; 12 | public byte Volume; 13 | public sbyte Panpot; 14 | public uint Rest; 15 | public ushort PitchBend; 16 | public long CurOffset; 17 | public long LoopOffset; 18 | public bool Stopped; 19 | public uint LastNoteDuration; 20 | public uint LastRest; 21 | 22 | public readonly List Channels = new List(0x10); 23 | 24 | public Track(byte i, long startOffset) 25 | { 26 | Index = i; 27 | _startOffset = startOffset; 28 | } 29 | 30 | public void Init() 31 | { 32 | Expression = 0; 33 | Voice = 0; 34 | Volume = 0; 35 | Octave = 4; 36 | Panpot = 0; 37 | Rest = 0; 38 | PitchBend = 0; 39 | CurOffset = _startOffset; 40 | LoopOffset = -1; 41 | Stopped = false; 42 | LastNoteDuration = 0; 43 | LastRest = 0; 44 | StopAllChannels(); 45 | } 46 | 47 | public void Tick() 48 | { 49 | if (Rest > 0) 50 | { 51 | Rest--; 52 | } 53 | for (int i = 0; i < Channels.Count; i++) 54 | { 55 | Channel c = Channels[i]; 56 | if (c.NoteLength > 0) 57 | { 58 | c.NoteLength--; 59 | } 60 | } 61 | } 62 | 63 | public void StopAllChannels() 64 | { 65 | Channel[] chans = Channels.ToArray(); 66 | for (int i = 0; i < chans.Length; i++) 67 | { 68 | chans[i].Stop(); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /VG Music Studio/Core/NDS/DSE/Utils.cs: -------------------------------------------------------------------------------- 1 | namespace Kermalis.VGMusicStudio.Core.NDS.DSE 2 | { 3 | internal static class Utils 4 | { 5 | public static short[] Duration16 = new short[128] 6 | { 7 | 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 8 | 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, 9 | 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 10 | 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, 11 | 0x0020, 0x0023, 0x0028, 0x002D, 0x0033, 0x0039, 0x0040, 0x0048, 12 | 0x0050, 0x0058, 0x0062, 0x006D, 0x0078, 0x0083, 0x0090, 0x009E, 13 | 0x00AC, 0x00BC, 0x00CC, 0x00DE, 0x00F0, 0x0104, 0x0119, 0x012F, 14 | 0x0147, 0x0160, 0x017A, 0x0196, 0x01B3, 0x01D2, 0x01F2, 0x0214, 15 | 0x0238, 0x025E, 0x0285, 0x02AE, 0x02D9, 0x0307, 0x0336, 0x0367, 16 | 0x039B, 0x03D1, 0x0406, 0x0442, 0x047E, 0x04C4, 0x0500, 0x0546, 17 | 0x058C, 0x0622, 0x0672, 0x06CC, 0x071C, 0x0776, 0x07DA, 0x0834, 18 | 0x0898, 0x0906, 0x096A, 0x09D8, 0x0A50, 0x0ABE, 0x0B40, 0x0BB8, 19 | 0x0C3A, 0x0CBC, 0x0D48, 0x0DDE, 0x0E6A, 0x0F00, 0x0FA0, 0x1040, 20 | 0x10EA, 0x1194, 0x123E, 0x12F2, 0x13B0, 0x146E, 0x1536, 0x15FE, 21 | 0x16D0, 0x17A2, 0x187E, 0x195A, 0x1A40, 0x1B30, 0x1C20, 0x1D1A, 22 | 0x1E1E, 0x1F22, 0x2030, 0x2148, 0x2260, 0x2382, 0x2710, 0x7FFF 23 | }; 24 | public static int[] Duration32 = new int[128] 25 | { 26 | 0x00000000, 0x00000004, 0x00000007, 0x0000000A, 0x0000000F, 0x00000015, 0x0000001C, 0x00000024, 27 | 0x0000002E, 0x0000003A, 0x00000048, 0x00000057, 0x00000068, 0x0000007B, 0x00000091, 0x000000A8, 28 | 0x00000185, 0x000001BE, 0x000001FC, 0x0000023F, 0x00000288, 0x000002D6, 0x0000032A, 0x00000385, 29 | 0x000003E5, 0x0000044C, 0x000004BA, 0x0000052E, 0x000005A9, 0x0000062C, 0x000006B5, 0x00000746, 30 | 0x00000BCF, 0x00000CC0, 0x00000DBD, 0x00000EC6, 0x00000FDC, 0x000010FF, 0x0000122F, 0x0000136C, 31 | 0x000014B6, 0x0000160F, 0x00001775, 0x000018EA, 0x00001A6D, 0x00001BFF, 0x00001DA0, 0x00001F51, 32 | 0x00002C16, 0x00002E80, 0x00003100, 0x00003395, 0x00003641, 0x00003902, 0x00003BDB, 0x00003ECA, 33 | 0x000041D0, 0x000044EE, 0x00004824, 0x00004B73, 0x00004ED9, 0x00005259, 0x000055F2, 0x000059A4, 34 | 0x000074CC, 0x000079AB, 0x00007EAC, 0x000083CE, 0x00008911, 0x00008E77, 0x000093FF, 0x000099AA, 35 | 0x00009F78, 0x0000A56A, 0x0000AB80, 0x0000B1BB, 0x0000B81A, 0x0000BE9E, 0x0000C547, 0x0000CC17, 36 | 0x0000FD42, 0x000105CB, 0x00010E82, 0x00011768, 0x0001207E, 0x000129C4, 0x0001333B, 0x00013CE2, 37 | 0x000146BB, 0x000150C5, 0x00015B02, 0x00016572, 0x00017015, 0x00017AEB, 0x000185F5, 0x00019133, 38 | 0x0001E16D, 0x0001EF07, 0x0001FCE0, 0x00020AF7, 0x0002194F, 0x000227E6, 0x000236BE, 0x000245D7, 39 | 0x00025532, 0x000264CF, 0x000274AE, 0x000284D0, 0x00029536, 0x0002A5E0, 0x0002B6CE, 0x0002C802, 40 | 0x000341B0, 0x000355F8, 0x00036A90, 0x00037F79, 0x000394B4, 0x0003AA41, 0x0003C021, 0x0003D654, 41 | 0x0003ECDA, 0x000403B5, 0x00041AE5, 0x0004326A, 0x00044A45, 0x00046277, 0x00047B00, 0x7FFFFFFF 42 | }; 43 | public static readonly byte[] FixedRests = new byte[0x10] 44 | { 45 | 96, 72, 64, 48, 36, 32, 24, 18, 16, 12, 9, 8, 6, 4, 3, 2 46 | }; 47 | 48 | public static bool IsStateRemovable(EnvelopeState state) 49 | { 50 | return state == EnvelopeState.Two || state >= EnvelopeState.Seven; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /VG Music Studio/Core/NDS/SDAT/Config.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.VGMusicStudio.Properties; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Kermalis.VGMusicStudio.Core.NDS.SDAT 6 | { 7 | internal class Config : Core.Config 8 | { 9 | public readonly SDAT SDAT; 10 | 11 | public Config(SDAT sdat) 12 | { 13 | if (sdat.INFOBlock.SequenceInfos.NumEntries == 0) 14 | { 15 | throw new Exception(Strings.ErrorSDATNoSequences); 16 | } 17 | SDAT = sdat; 18 | var songs = new List(sdat.INFOBlock.SequenceInfos.NumEntries); 19 | for (int i = 0; i < sdat.INFOBlock.SequenceInfos.NumEntries; i++) 20 | { 21 | if (sdat.INFOBlock.SequenceInfos.Entries[i] != null) 22 | { 23 | songs.Add(new Song(i, sdat.SYMBBlock is null ? i.ToString() : sdat.SYMBBlock.SequenceSymbols.Entries[i])); 24 | } 25 | } 26 | Playlists.Add(new Playlist(Strings.PlaylistMusic, songs)); 27 | } 28 | 29 | public override string GetGameName() 30 | { 31 | return "SDAT"; 32 | } 33 | public override string GetSongName(long index) 34 | { 35 | return SDAT.SYMBBlock is null || index < 0 || index >= SDAT.SYMBBlock.SequenceSymbols.NumEntries 36 | ? index.ToString() 37 | : '\"' + SDAT.SYMBBlock.SequenceSymbols.Entries[index] + '\"'; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /VG Music Studio/Core/NDS/SDAT/Enums.cs: -------------------------------------------------------------------------------- 1 | namespace Kermalis.VGMusicStudio.Core.NDS.SDAT 2 | { 3 | internal enum EnvelopeState : byte 4 | { 5 | Attack, 6 | Decay, 7 | Sustain, 8 | Release 9 | } 10 | internal enum ArgType : byte 11 | { 12 | None, 13 | Byte, 14 | Short, 15 | VarLen, 16 | Rand, 17 | PlayerVar 18 | } 19 | 20 | internal enum LFOType : byte 21 | { 22 | Pitch, 23 | Volume, 24 | Panpot 25 | } 26 | internal enum InstrumentType : byte 27 | { 28 | PCM = 0x1, 29 | PSG = 0x2, 30 | Noise = 0x3, 31 | Drum = 0x10, 32 | KeySplit = 0x11 33 | } 34 | internal enum SWAVFormat : byte 35 | { 36 | PCM8, 37 | PCM16, 38 | ADPCM 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /VG Music Studio/Core/NDS/SDAT/FileHeader.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.EndianBinaryIO; 2 | using System; 3 | 4 | namespace Kermalis.VGMusicStudio.Core.NDS.SDAT 5 | { 6 | internal class FileHeader : IBinarySerializable 7 | { 8 | public string FileType; 9 | public ushort Endianness; 10 | public ushort Version; 11 | public int FileSize; 12 | public ushort HeaderSize; // 16 13 | public ushort NumBlocks; 14 | 15 | public void Read(EndianBinaryReader er) 16 | { 17 | FileType = er.ReadString(4, false); 18 | er.Endianness = EndianBinaryIO.Endianness.BigEndian; 19 | Endianness = er.ReadUInt16(); 20 | er.Endianness = Endianness == 0xFFFE ? EndianBinaryIO.Endianness.LittleEndian : EndianBinaryIO.Endianness.BigEndian; 21 | Version = er.ReadUInt16(); 22 | FileSize = er.ReadInt32(); 23 | HeaderSize = er.ReadUInt16(); 24 | NumBlocks = er.ReadUInt16(); 25 | } 26 | public void Write(EndianBinaryWriter ew) 27 | { 28 | throw new NotImplementedException(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /VG Music Studio/Core/NDS/SDAT/SBNK.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.EndianBinaryIO; 2 | using System; 3 | using System.IO; 4 | 5 | namespace Kermalis.VGMusicStudio.Core.NDS.SDAT 6 | { 7 | internal class SBNK 8 | { 9 | public class InstrumentData 10 | { 11 | public class DataParam 12 | { 13 | [BinaryArrayFixedLength(2)] 14 | public ushort[] Info { get; set; } 15 | public byte BaseKey { get; set; } 16 | public byte Attack { get; set; } 17 | public byte Decay { get; set; } 18 | public byte Sustain { get; set; } 19 | public byte Release { get; set; } 20 | public byte Pan { get; set; } 21 | } 22 | 23 | public InstrumentType Type { get; set; } 24 | public byte Padding { get; set; } 25 | public DataParam Param { get; set; } 26 | } 27 | public class Instrument : IBinarySerializable 28 | { 29 | public class DefaultData 30 | { 31 | public InstrumentData.DataParam Param { get; set; } 32 | } 33 | public class DrumSetData : IBinarySerializable 34 | { 35 | public byte MinNote; 36 | public byte MaxNote; 37 | public InstrumentData[] SubInstruments; 38 | 39 | public void Read(EndianBinaryReader er) 40 | { 41 | MinNote = er.ReadByte(); 42 | MaxNote = er.ReadByte(); 43 | SubInstruments = new InstrumentData[MaxNote - MinNote + 1]; 44 | for (int i = 0; i < SubInstruments.Length; i++) 45 | { 46 | SubInstruments[i] = er.ReadObject(); 47 | } 48 | } 49 | public void Write(EndianBinaryWriter ew) 50 | { 51 | throw new NotImplementedException(); 52 | } 53 | } 54 | public class KeySplitData : IBinarySerializable 55 | { 56 | public byte[] KeyRegions; 57 | public InstrumentData[] SubInstruments; 58 | 59 | public void Read(EndianBinaryReader er) 60 | { 61 | KeyRegions = er.ReadBytes(8); 62 | int numSubInstruments = 0; 63 | for (int i = 0; i < 8; i++) 64 | { 65 | if (KeyRegions[i] == 0) 66 | { 67 | break; 68 | } 69 | numSubInstruments++; 70 | } 71 | SubInstruments = new InstrumentData[numSubInstruments]; 72 | for (int i = 0; i < numSubInstruments; i++) 73 | { 74 | SubInstruments[i] = er.ReadObject(); 75 | } 76 | } 77 | public void Write(EndianBinaryWriter ew) 78 | { 79 | throw new NotImplementedException(); 80 | } 81 | } 82 | 83 | public InstrumentType Type; 84 | public ushort DataOffset; 85 | public byte Padding; 86 | 87 | public object Data; 88 | 89 | public void Read(EndianBinaryReader er) 90 | { 91 | Type = er.ReadEnum(); 92 | DataOffset = er.ReadUInt16(); 93 | Padding = er.ReadByte(); 94 | 95 | long p = er.BaseStream.Position; 96 | switch (Type) 97 | { 98 | case InstrumentType.PCM: 99 | case InstrumentType.PSG: 100 | case InstrumentType.Noise: Data = er.ReadObject(DataOffset); break; 101 | case InstrumentType.Drum: Data = er.ReadObject(DataOffset); break; 102 | case InstrumentType.KeySplit: Data = er.ReadObject(DataOffset); break; 103 | default: break; 104 | } 105 | er.BaseStream.Position = p; 106 | } 107 | public void Write(EndianBinaryWriter ew) 108 | { 109 | throw new NotImplementedException(); 110 | } 111 | } 112 | 113 | public FileHeader FileHeader { get; set; } // "SBNK" 114 | [BinaryStringFixedLength(4)] 115 | public string BlockType { get; set; } // "DATA" 116 | public int BlockSize { get; set; } 117 | [BinaryArrayFixedLength(32)] 118 | public byte[] Padding { get; set; } 119 | public int NumInstruments { get; set; } 120 | [BinaryArrayVariableLength(nameof(NumInstruments))] 121 | public Instrument[] Instruments { get; set; } 122 | 123 | [BinaryIgnore] 124 | public SWAR[] SWARs { get; } = new SWAR[4]; 125 | 126 | public SBNK(byte[] bytes) 127 | { 128 | using (var er = new EndianBinaryReader(new MemoryStream(bytes))) 129 | { 130 | er.ReadIntoObject(this); 131 | } 132 | } 133 | 134 | public InstrumentData GetInstrumentData(int voice, int key) 135 | { 136 | if (voice >= NumInstruments) 137 | { 138 | return null; 139 | } 140 | else 141 | { 142 | switch (Instruments[voice].Type) 143 | { 144 | case InstrumentType.PCM: 145 | case InstrumentType.PSG: 146 | case InstrumentType.Noise: 147 | { 148 | var d = (Instrument.DefaultData)Instruments[voice].Data; 149 | // TODO: Better way? 150 | return new InstrumentData 151 | { 152 | Type = Instruments[voice].Type, 153 | Param = d.Param 154 | }; 155 | } 156 | case InstrumentType.Drum: 157 | { 158 | var d = (Instrument.DrumSetData)Instruments[voice].Data; 159 | return key < d.MinNote || key > d.MaxNote ? null : d.SubInstruments[key - d.MinNote]; 160 | } 161 | case InstrumentType.KeySplit: 162 | { 163 | var d = (Instrument.KeySplitData)Instruments[voice].Data; 164 | for (int i = 0; i < 8; i++) 165 | { 166 | if (key <= d.KeyRegions[i]) 167 | { 168 | return d.SubInstruments[i]; 169 | } 170 | } 171 | return null; 172 | } 173 | default: return null; 174 | } 175 | } 176 | } 177 | 178 | public SWAR.SWAV GetSWAV(int swarIndex, int swavIndex) 179 | { 180 | SWAR swar = SWARs[swarIndex]; 181 | return swar == null || swavIndex >= swar.NumWaves ? null : swar.Waves[swavIndex]; 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /VG Music Studio/Core/NDS/SDAT/SSEQ.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.EndianBinaryIO; 2 | using System.IO; 3 | 4 | namespace Kermalis.VGMusicStudio.Core.NDS.SDAT 5 | { 6 | internal class SSEQ 7 | { 8 | public FileHeader FileHeader; // "SSEQ" 9 | public string BlockType; // "DATA" 10 | public int BlockSize; 11 | public int DataOffset; 12 | 13 | public byte[] Data; 14 | 15 | public SSEQ(byte[] bytes) 16 | { 17 | using (var er = new EndianBinaryReader(new MemoryStream(bytes))) 18 | { 19 | FileHeader = er.ReadObject(); 20 | BlockType = er.ReadString(4, false); 21 | BlockSize = er.ReadInt32(); 22 | DataOffset = er.ReadInt32(); 23 | 24 | Data = er.ReadBytes(FileHeader.FileSize - DataOffset, DataOffset); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /VG Music Studio/Core/NDS/SDAT/SWAR.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.EndianBinaryIO; 2 | using System; 3 | using System.IO; 4 | 5 | namespace Kermalis.VGMusicStudio.Core.NDS.SDAT 6 | { 7 | internal class SWAR 8 | { 9 | public class SWAV : IBinarySerializable 10 | { 11 | public SWAVFormat Format; 12 | public bool DoesLoop; 13 | public ushort SampleRate; 14 | public ushort Timer; // (NDSUtils.ARM7_CLOCK / SampleRate) 15 | public ushort LoopOffset; 16 | public int Length; 17 | 18 | public byte[] Samples; 19 | 20 | public void Read(EndianBinaryReader er) 21 | { 22 | Format = er.ReadEnum(); 23 | DoesLoop = er.ReadBoolean(); 24 | SampleRate = er.ReadUInt16(); 25 | Timer = er.ReadUInt16(); 26 | LoopOffset = er.ReadUInt16(); 27 | Length = er.ReadInt32(); 28 | 29 | Samples = er.ReadBytes((LoopOffset * 4) + (Length * 4)); 30 | } 31 | public void Write(EndianBinaryWriter ew) 32 | { 33 | throw new NotImplementedException(); 34 | } 35 | } 36 | 37 | public FileHeader FileHeader; // "SWAR" 38 | public string BlockType; // "DATA" 39 | public int BlockSize; 40 | public byte[] Padding; 41 | public int NumWaves; 42 | public int[] WaveOffsets; 43 | 44 | public SWAV[] Waves; 45 | 46 | public SWAR(byte[] bytes) 47 | { 48 | using (var er = new EndianBinaryReader(new MemoryStream(bytes))) 49 | { 50 | FileHeader = er.ReadObject(); 51 | BlockType = er.ReadString(4, false); 52 | BlockSize = er.ReadInt32(); 53 | Padding = er.ReadBytes(32); 54 | NumWaves = er.ReadInt32(); 55 | WaveOffsets = er.ReadInt32s(NumWaves); 56 | 57 | Waves = new SWAV[NumWaves]; 58 | for (int i = 0; i < NumWaves; i++) 59 | { 60 | Waves[i] = er.ReadObject(WaveOffsets[i]); 61 | } 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /VG Music Studio/Core/NDS/SDAT/Track.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Kermalis.VGMusicStudio.Core.NDS.SDAT 4 | { 5 | internal class Track 6 | { 7 | public readonly byte Index; 8 | private readonly Player _player; 9 | 10 | public bool Allocated; 11 | public bool Enabled; 12 | public bool Stopped; 13 | public bool Tie; 14 | public bool Mono; 15 | public bool Portamento; 16 | public bool WaitingForNoteToFinishBeforeContinuingXD; // Is this necessary? 17 | public byte Voice; 18 | public byte Priority; 19 | public byte Volume; 20 | public byte Expression; 21 | public byte PitchBendRange; 22 | public byte LFORange; 23 | public byte LFOSpeed; 24 | public byte LFODepth; 25 | public ushort LFODelay; 26 | public ushort LFOPhase; 27 | public int LFOParam; 28 | public ushort LFODelayCount; 29 | public LFOType LFOType; 30 | public sbyte PitchBend; 31 | public sbyte Panpot; 32 | public sbyte Transpose; 33 | public byte Attack; 34 | public byte Decay; 35 | public byte Sustain; 36 | public byte Release; 37 | public byte PortamentoKey; 38 | public byte PortamentoTime; 39 | public short SweepPitch; 40 | public int Rest; 41 | public int[] CallStack = new int[3]; 42 | public byte[] CallStackLoops = new byte[3]; 43 | public byte CallStackDepth; 44 | public int DataOffset; 45 | public bool VariableFlag; // Set by variable commands (0xB0 - 0xBD) 46 | public bool DoCommandWork; 47 | public ArgType ArgOverrideType; 48 | 49 | public readonly List Channels = new List(0x10); 50 | 51 | public int GetPitch() 52 | { 53 | //int lfo = LFOType == LFOType.Pitch ? LFOParam : 0; 54 | int lfo = 0; 55 | return (PitchBend * PitchBendRange / 2) + lfo; 56 | } 57 | public int GetVolume() 58 | { 59 | //int lfo = LFOType == LFOType.Volume ? LFOParam : 0; 60 | int lfo = 0; 61 | return Utils.SustainTable[_player.Volume] + Utils.SustainTable[Volume] + Utils.SustainTable[Expression] + lfo; 62 | } 63 | public sbyte GetPan() 64 | { 65 | //int lfo = LFOType == LFOType.Panpot ? LFOParam : 0; 66 | int lfo = 0; 67 | int p = Panpot + lfo; 68 | if (p < -0x40) 69 | { 70 | p = -0x40; 71 | } 72 | else if (p > 0x3F) 73 | { 74 | p = 0x3F; 75 | } 76 | return (sbyte)p; 77 | } 78 | 79 | public Track(byte i, Player player) 80 | { 81 | Index = i; 82 | _player = player; 83 | } 84 | public void Init() 85 | { 86 | Stopped = Tie = WaitingForNoteToFinishBeforeContinuingXD = Portamento = false; 87 | Allocated = Enabled = Index == 0; 88 | DataOffset = 0; 89 | ArgOverrideType = ArgType.None; 90 | Mono = VariableFlag = DoCommandWork = true; 91 | CallStackDepth = 0; 92 | Voice = LFODepth = 0; 93 | PitchBend = Panpot = Transpose = 0; 94 | LFOPhase = LFODelay = LFODelayCount = 0; 95 | LFORange = 1; 96 | LFOSpeed = 0x10; 97 | Priority = (byte)(_player.Priority + 0x40); 98 | Volume = Expression = 0x7F; 99 | Attack = Decay = Sustain = Release = 0xFF; 100 | PitchBendRange = 2; 101 | PortamentoKey = 60; 102 | PortamentoTime = 0; 103 | SweepPitch = 0; 104 | LFOType = LFOType.Pitch; 105 | Rest = 0; 106 | StopAllChannels(); 107 | } 108 | public void LFOTick() 109 | { 110 | if (Channels.Count != 0) 111 | { 112 | if (LFODelayCount > 0) 113 | { 114 | LFODelayCount--; 115 | LFOPhase = 0; 116 | } 117 | else 118 | { 119 | int param = LFORange * Utils.Sin(LFOPhase >> 8) * LFODepth; 120 | if (LFOType == LFOType.Volume) 121 | { 122 | param = (param * 60) >> 14; 123 | } 124 | else 125 | { 126 | param >>= 8; 127 | } 128 | LFOParam = param; 129 | int counter = LFOPhase + (LFOSpeed << 6); // "<< 6" is "* 0x40" 130 | while (counter >= 0x8000) 131 | { 132 | counter -= 0x8000; 133 | } 134 | LFOPhase = (ushort)counter; 135 | } 136 | } 137 | else 138 | { 139 | LFOPhase = 0; 140 | LFOParam = 0; 141 | LFODelayCount = LFODelay; 142 | } 143 | } 144 | public void Tick() 145 | { 146 | if (Rest > 0) 147 | { 148 | Rest--; 149 | } 150 | if (Channels.Count != 0) 151 | { 152 | // TickNotes: 153 | for (int i = 0; i < Channels.Count; i++) 154 | { 155 | Channel c = Channels[i]; 156 | if (c.NoteDuration > 0) 157 | { 158 | c.NoteDuration--; 159 | } 160 | if (!c.AutoSweep && c.SweepCounter < c.SweepLength) 161 | { 162 | c.SweepCounter++; 163 | } 164 | } 165 | } 166 | else 167 | { 168 | WaitingForNoteToFinishBeforeContinuingXD = false; 169 | } 170 | } 171 | public void UpdateChannels() 172 | { 173 | for (int i = 0; i < Channels.Count; i++) 174 | { 175 | Channel c = Channels[i]; 176 | c.LFOType = LFOType; 177 | c.LFOSpeed = LFOSpeed; 178 | c.LFODepth = LFODepth; 179 | c.LFORange = LFORange; 180 | c.LFODelay = LFODelay; 181 | } 182 | } 183 | 184 | public void StopAllChannels() 185 | { 186 | Channel[] chans = Channels.ToArray(); 187 | for (int i = 0; i < chans.Length; i++) 188 | { 189 | chans[i].Stop(); 190 | } 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /VG Music Studio/Core/NDS/Utils.cs: -------------------------------------------------------------------------------- 1 | namespace Kermalis.VGMusicStudio.Core.NDS 2 | { 3 | internal static class Utils 4 | { 5 | public const int ARM7_CLOCK = 16756991; // (33.513982 MHz / 2) == 16.756991 MHz == 16,756,991 Hz 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /VG Music Studio/Core/Player.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Kermalis.VGMusicStudio.Core 5 | { 6 | internal enum PlayerState : byte 7 | { 8 | Stopped = 0, 9 | Playing, 10 | Paused, 11 | Recording, 12 | ShutDown 13 | } 14 | 15 | internal delegate void SongEndedEvent(); 16 | 17 | internal interface IPlayer : IDisposable 18 | { 19 | List[] Events { get; } 20 | long MaxTicks { get; } 21 | long ElapsedTicks { get; } 22 | bool ShouldFadeOut { get; set; } 23 | long NumLoops { get; set; } 24 | 25 | PlayerState State { get; } 26 | event SongEndedEvent SongEnded; 27 | 28 | void LoadSong(long index); 29 | void SetCurrentPosition(long ticks); 30 | void Play(); 31 | void Pause(); 32 | void Stop(); 33 | void Record(string fileName); 34 | void GetSongState(UI.SongInfoControl.SongInfo info); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /VG Music Studio/Core/SongEvent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Drawing; 3 | 4 | namespace Kermalis.VGMusicStudio.Core 5 | { 6 | internal interface ICommand 7 | { 8 | Color Color { get; } 9 | string Label { get; } 10 | string Arguments { get; } 11 | } 12 | internal class SongEvent 13 | { 14 | public long Offset { get; } 15 | public List Ticks { get; } = new List(); 16 | public ICommand Command { get; } 17 | 18 | public SongEvent(long offset, ICommand command) 19 | { 20 | Offset = offset; 21 | Command = command; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /VG Music Studio/Core/VGMSDebug.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.EndianBinaryIO; 2 | using Sanford.Multimedia.Midi; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | 8 | namespace Kermalis.VGMusicStudio.Core 9 | { 10 | #if DEBUG 11 | internal static class VGMSDebug 12 | { 13 | public static void MIDIVolumeMerger(string f1, string f2) 14 | { 15 | var midi1 = new Sequence(f1); 16 | var midi2 = new Sequence(f2); 17 | var baby = new Sequence(midi1.Division); 18 | 19 | for (int i = 0; i < midi1.Count; i++) 20 | { 21 | Track midi1Track = midi1[i]; 22 | Track midi2Track = midi2[i]; 23 | var babyTrack = new Track(); 24 | baby.Add(babyTrack); 25 | 26 | for (int j = 0; j < midi1Track.Count; j++) 27 | { 28 | MidiEvent e1 = midi1Track.GetMidiEvent(j); 29 | if (e1.MidiMessage is ChannelMessage cm1 && cm1.Command == ChannelCommand.Controller && cm1.Data1 == (int)ControllerType.Volume) 30 | { 31 | MidiEvent e2 = midi2Track.GetMidiEvent(j); 32 | var cm2 = (ChannelMessage)e2.MidiMessage; 33 | babyTrack.Insert(e1.AbsoluteTicks, new ChannelMessage(ChannelCommand.Controller, cm1.MidiChannel, (int)ControllerType.Volume, Math.Max(cm1.Data2, cm2.Data2))); 34 | } 35 | else 36 | { 37 | babyTrack.Insert(e1.AbsoluteTicks, e1.MidiMessage); 38 | } 39 | } 40 | } 41 | 42 | baby.Save(f1); 43 | baby.Save(f2); 44 | } 45 | 46 | public static void EventScan(List songs, bool showIndexes) 47 | { 48 | Console.WriteLine($"{nameof(EventScan)} started."); 49 | var scans = new Dictionary>(); 50 | foreach (Config.Song song in songs) 51 | { 52 | try 53 | { 54 | Engine.Instance.Player.LoadSong(song.Index); 55 | } 56 | catch (Exception ex) 57 | { 58 | Console.WriteLine("Exception loading {0} - {1}", showIndexes ? $"song {song.Index}" : $"\"{song.Name}\"", ex.Message); 59 | continue; 60 | } 61 | if (Engine.Instance.Player.Events != null) 62 | { 63 | foreach (string cmd in Engine.Instance.Player.Events.Where(ev => ev != null).SelectMany(ev => ev).Select(ev => ev.Command.Label).Distinct()) 64 | { 65 | if (scans.ContainsKey(cmd)) 66 | { 67 | scans[cmd].Add(song); 68 | } 69 | else 70 | { 71 | scans.Add(cmd, new List() { song }); 72 | } 73 | } 74 | } 75 | } 76 | foreach (KeyValuePair> kvp in scans.OrderBy(k => k.Key)) 77 | { 78 | Console.WriteLine("{0} ({1})", kvp.Key, showIndexes ? string.Join(", ", kvp.Value.Select(s => s.Index)) : string.Join(", ", kvp.Value.Select(s => s.Name))); 79 | } 80 | Console.WriteLine($"{nameof(EventScan)} ended."); 81 | } 82 | 83 | public static void GBAGameCodeScan(string path) 84 | { 85 | Console.WriteLine($"{nameof(GBAGameCodeScan)} started."); 86 | var scans = new List(); 87 | foreach (string file in Directory.GetFiles(path, "*.gba", SearchOption.AllDirectories)) 88 | { 89 | try 90 | { 91 | using (var reader = new EndianBinaryReader(File.OpenRead(file))) 92 | { 93 | string gameCode = reader.ReadString(3, false, 0xAC); 94 | char regionCode = reader.ReadChar(0xAF); 95 | byte version = reader.ReadByte(0xBC); 96 | scans.Add(string.Format("Code: {0}\tRegion: {1}\tVersion: {2}\tFile: {3}", gameCode, regionCode, version, file)); 97 | } 98 | } 99 | catch (Exception ex) 100 | { 101 | Console.WriteLine("Exception loading \"{0}\" - {1}", file, ex.Message); 102 | } 103 | } 104 | foreach (string s in scans.OrderBy(s => s)) 105 | { 106 | Console.WriteLine(s); 107 | } 108 | Console.WriteLine($"{nameof(GBAGameCodeScan)} ended."); 109 | } 110 | } 111 | #endif 112 | } 113 | -------------------------------------------------------------------------------- /VG Music Studio/Dependencies/DLS2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Dependencies/DLS2.dll -------------------------------------------------------------------------------- /VG Music Studio/Dependencies/Sanford.Multimedia.Midi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Dependencies/Sanford.Multimedia.Midi.dll -------------------------------------------------------------------------------- /VG Music Studio/Dependencies/SoundFont2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Dependencies/SoundFont2.dll -------------------------------------------------------------------------------- /VG Music Studio/Program.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.VGMusicStudio.Core; 2 | using Kermalis.VGMusicStudio.Properties; 3 | using Kermalis.VGMusicStudio.UI; 4 | using System; 5 | using System.Windows.Forms; 6 | 7 | namespace Kermalis.VGMusicStudio 8 | { 9 | internal static class Program 10 | { 11 | [STAThread] 12 | private static void Main() 13 | { 14 | #if DEBUG 15 | //Debug.GBAGameCodeScan(@"C:\Users\Kermalis\Documents\Emulation\GBA\Games"); 16 | #endif 17 | try 18 | { 19 | GlobalConfig.Init(); 20 | } 21 | catch (Exception ex) 22 | { 23 | FlexibleMessageBox.Show(ex, Strings.ErrorGlobalConfig); 24 | return; 25 | } 26 | Application.EnableVisualStyles(); 27 | Application.Run(MainForm.Instance); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /VG Music Studio/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Resources; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("VG Music Studio")] 10 | [assembly: AssemblyDescription("Listen to the music from popular video game formats.")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("Kermalis")] 13 | [assembly: AssemblyProduct("VG Music Studio")] 14 | [assembly: AssemblyCopyright("Copyright © Kermalis 2019")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | 18 | // Setting ComVisible to false makes the types in this assembly not visible 19 | // to COM components. If you need to access a type in this assembly from 20 | // COM, set the ComVisible attribute to true on that type. 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | [assembly: Guid("97c8acf8-66a3-4321-91d6-3e94eaca577f")] 25 | 26 | // Version information for an assembly consists of the following four values: 27 | // 28 | // Major Version 29 | // Minor Version 30 | // Build Number 31 | // Revision 32 | // 33 | // You can specify all the values or you can default the Build and Revision Numbers 34 | // by using the '*' as shown below: 35 | // [assembly: AssemblyVersion("1.0.*")] 36 | [assembly: AssemblyVersion("0.0.0.2")] 37 | [assembly: AssemblyFileVersion("0.0.0.2")] 38 | [assembly: NeutralResourcesLanguage("en-US")] 39 | 40 | -------------------------------------------------------------------------------- /VG Music Studio/Properties/Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Properties/Icon.ico -------------------------------------------------------------------------------- /VG Music Studio/Properties/Icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Properties/Icon16.png -------------------------------------------------------------------------------- /VG Music Studio/Properties/Icon24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Properties/Icon24.png -------------------------------------------------------------------------------- /VG Music Studio/Properties/Icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Properties/Icon32.png -------------------------------------------------------------------------------- /VG Music Studio/Properties/Icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Properties/Icon48.png -------------------------------------------------------------------------------- /VG Music Studio/Properties/Icon528.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Properties/Icon528.png -------------------------------------------------------------------------------- /VG Music Studio/Properties/Next.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Properties/Next.ico -------------------------------------------------------------------------------- /VG Music Studio/Properties/Next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Properties/Next.png -------------------------------------------------------------------------------- /VG Music Studio/Properties/Pause.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Properties/Pause.ico -------------------------------------------------------------------------------- /VG Music Studio/Properties/Pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Properties/Pause.png -------------------------------------------------------------------------------- /VG Music Studio/Properties/Play.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Properties/Play.ico -------------------------------------------------------------------------------- /VG Music Studio/Properties/Play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Properties/Play.png -------------------------------------------------------------------------------- /VG Music Studio/Properties/Playlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Properties/Playlist.png -------------------------------------------------------------------------------- /VG Music Studio/Properties/Previous.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Properties/Previous.ico -------------------------------------------------------------------------------- /VG Music Studio/Properties/Previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Properties/Previous.png -------------------------------------------------------------------------------- /VG Music Studio/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Kermalis.VGMusicStudio.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Kermalis.VGMusicStudio.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 65 | /// 66 | internal static System.Drawing.Icon Icon { 67 | get { 68 | object obj = ResourceManager.GetObject("Icon", resourceCulture); 69 | return ((System.Drawing.Icon)(obj)); 70 | } 71 | } 72 | 73 | /// 74 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 75 | /// 76 | internal static System.Drawing.Icon IconNext { 77 | get { 78 | object obj = ResourceManager.GetObject("IconNext", resourceCulture); 79 | return ((System.Drawing.Icon)(obj)); 80 | } 81 | } 82 | 83 | /// 84 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 85 | /// 86 | internal static System.Drawing.Icon IconPause { 87 | get { 88 | object obj = ResourceManager.GetObject("IconPause", resourceCulture); 89 | return ((System.Drawing.Icon)(obj)); 90 | } 91 | } 92 | 93 | /// 94 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 95 | /// 96 | internal static System.Drawing.Icon IconPlay { 97 | get { 98 | object obj = ResourceManager.GetObject("IconPlay", resourceCulture); 99 | return ((System.Drawing.Icon)(obj)); 100 | } 101 | } 102 | 103 | /// 104 | /// Looks up a localized resource of type System.Drawing.Bitmap. 105 | /// 106 | internal static System.Drawing.Bitmap IconPlaylist { 107 | get { 108 | object obj = ResourceManager.GetObject("IconPlaylist", resourceCulture); 109 | return ((System.Drawing.Bitmap)(obj)); 110 | } 111 | } 112 | 113 | /// 114 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 115 | /// 116 | internal static System.Drawing.Icon IconPrevious { 117 | get { 118 | object obj = ResourceManager.GetObject("IconPrevious", resourceCulture); 119 | return ((System.Drawing.Icon)(obj)); 120 | } 121 | } 122 | 123 | /// 124 | /// Looks up a localized resource of type System.Drawing.Bitmap. 125 | /// 126 | internal static System.Drawing.Bitmap IconSong { 127 | get { 128 | object obj = ResourceManager.GetObject("IconSong", resourceCulture); 129 | return ((System.Drawing.Bitmap)(obj)); 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /VG Music Studio/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Properties\Icon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | 125 | Next.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 126 | 127 | 128 | Pause.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 129 | 130 | 131 | Play.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 132 | 133 | 134 | ..\Properties\Playlist.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 135 | 136 | 137 | Previous.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 138 | 139 | 140 | ..\Properties\Song.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 141 | 142 | -------------------------------------------------------------------------------- /VG Music Studio/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Kermalis.VGMusicStudio.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.7.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /VG Music Studio/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VG Music Studio/Properties/Song.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/Properties/Song.png -------------------------------------------------------------------------------- /VG Music Studio/UI/ImageComboBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Windows.Forms; 4 | 5 | namespace Kermalis.VGMusicStudio.UI 6 | { 7 | internal class ImageComboBox : ComboBox 8 | { 9 | private const int _imgSize = 15; 10 | private bool _open = false; 11 | 12 | public ImageComboBox() 13 | { 14 | DrawMode = DrawMode.OwnerDrawFixed; 15 | DropDownStyle = ComboBoxStyle.DropDown; 16 | } 17 | 18 | protected override void OnDrawItem(DrawItemEventArgs e) 19 | { 20 | e.DrawBackground(); 21 | e.DrawFocusRectangle(); 22 | 23 | if (e.Index >= 0) 24 | { 25 | ImageComboBoxItem item = Items[e.Index] as ImageComboBoxItem ?? throw new InvalidCastException($"Item was not of type \"{nameof(ImageComboBoxItem)}\""); 26 | int indent = _open ? item.IndentLevel : 0; 27 | e.Graphics.DrawImage(item.Image, e.Bounds.Left + (indent * _imgSize), e.Bounds.Top, _imgSize, _imgSize); 28 | e.Graphics.DrawString(item.ToString(), e.Font, new SolidBrush(e.ForeColor), e.Bounds.Left + (indent * _imgSize) + _imgSize, e.Bounds.Top); 29 | } 30 | 31 | base.OnDrawItem(e); 32 | } 33 | protected override void OnDropDown(EventArgs e) 34 | { 35 | _open = true; 36 | base.OnDropDown(e); 37 | } 38 | protected override void OnDropDownClosed(EventArgs e) 39 | { 40 | _open = false; 41 | base.OnDropDownClosed(e); 42 | } 43 | } 44 | internal class ImageComboBoxItem 45 | { 46 | public object Item { get; } 47 | public Image Image { get; } 48 | public int IndentLevel { get; } 49 | 50 | public ImageComboBoxItem(object item, Image image, int indentLevel) 51 | { 52 | Item = item; 53 | Image = image; 54 | IndentLevel = indentLevel; 55 | } 56 | 57 | public override string ToString() 58 | { 59 | return Item.ToString(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /VG Music Studio/UI/PianoControl.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | 3 | /* Copyright (c) 2006 Leslie Sanford 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to 7 | * deal in the Software without restriction, including without limitation the 8 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9 | * sell copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | */ 23 | 24 | #endregion 25 | 26 | using Kermalis.VGMusicStudio.Core; 27 | using Kermalis.VGMusicStudio.Util; 28 | using System; 29 | using System.ComponentModel; 30 | using System.Drawing; 31 | using System.Windows.Forms; 32 | 33 | namespace Kermalis.VGMusicStudio.UI 34 | { 35 | [DesignerCategory("")] 36 | internal class PianoControl : Control 37 | { 38 | private enum KeyType : byte 39 | { 40 | Black, 41 | White 42 | } 43 | private static readonly KeyType[] KeyTypeTable = new KeyType[12] 44 | { 45 | KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White 46 | }; 47 | private const double blackKeyScale = 2.0 / 3.0; 48 | 49 | public class PianoKey : Control 50 | { 51 | public bool Dirty; 52 | public bool Pressed; 53 | 54 | public readonly SolidBrush OnBrush = new SolidBrush(Color.Transparent); 55 | private readonly SolidBrush _offBrush; 56 | 57 | public PianoKey(byte k) 58 | { 59 | SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true); 60 | SetStyle(ControlStyles.Selectable, false); 61 | _offBrush = new SolidBrush(new HSLColor(160.0, 0.0, KeyTypeTable[k % 12] == KeyType.White ? k / 12 % 2 == 0 ? 240.0 : 120.0 : 0.0)); 62 | } 63 | 64 | protected override void Dispose(bool disposing) 65 | { 66 | if (disposing) 67 | { 68 | OnBrush.Dispose(); 69 | _offBrush.Dispose(); 70 | } 71 | base.Dispose(disposing); 72 | } 73 | protected override void OnPaint(PaintEventArgs e) 74 | { 75 | e.Graphics.FillRectangle(Pressed ? OnBrush : _offBrush, 1, 1, Width - 2, Height - 2); 76 | e.Graphics.DrawRectangle(Pens.Black, 0, 0, Width - 1, Height - 1); 77 | base.OnPaint(e); 78 | } 79 | } 80 | 81 | private readonly PianoKey[] _keys = new PianoKey[0x80]; 82 | public const int WhiteKeyCount = 75; 83 | public int WhiteKeyWidth; 84 | 85 | public PianoControl() 86 | { 87 | SetStyle(ControlStyles.Selectable, false); 88 | for (byte k = 0; k <= 0x7F; k++) 89 | { 90 | var key = new PianoKey(k); 91 | _keys[k] = key; 92 | if (KeyTypeTable[k % 12] == KeyType.Black) 93 | { 94 | key.BringToFront(); 95 | } 96 | Controls.Add(key); 97 | } 98 | SetKeySizes(); 99 | } 100 | private void SetKeySizes() 101 | { 102 | WhiteKeyWidth = Width / WhiteKeyCount; 103 | int blackKeyWidth = (int)(WhiteKeyWidth * blackKeyScale); 104 | int blackKeyHeight = (int)(Height * blackKeyScale); 105 | int offset = WhiteKeyWidth - (blackKeyWidth / 2); 106 | int w = 0; 107 | for (int k = 0; k <= 0x7F; k++) 108 | { 109 | PianoKey key = _keys[k]; 110 | if (KeyTypeTable[k % 12] == KeyType.White) 111 | { 112 | key.Height = Height; 113 | key.Width = WhiteKeyWidth; 114 | key.Location = new Point(w * WhiteKeyWidth, 0); 115 | w++; 116 | } 117 | else 118 | { 119 | key.Height = blackKeyHeight; 120 | key.Width = blackKeyWidth; 121 | key.Location = new Point(offset + ((w - 1) * WhiteKeyWidth)); 122 | key.BringToFront(); 123 | } 124 | } 125 | } 126 | 127 | public void UpdateKeys(SongInfoControl.SongInfo info, bool[] enabledTracks) 128 | { 129 | for (int k = 0; k <= 0x7F; k++) 130 | { 131 | PianoKey key = _keys[k]; 132 | key.Dirty = key.Pressed; 133 | key.Pressed = false; 134 | } 135 | for (int i = SongInfoControl.SongInfo.MaxTracks - 1; i >= 0; i--) 136 | { 137 | if (enabledTracks[i]) 138 | { 139 | SongInfoControl.SongInfo.Track tin = info.Tracks[i]; 140 | for (int nk = 0; nk < SongInfoControl.SongInfo.MaxKeys; nk++) 141 | { 142 | byte k = tin.Keys[nk]; 143 | if (k == byte.MaxValue) 144 | { 145 | break; 146 | } 147 | else 148 | { 149 | PianoKey key = _keys[k]; 150 | key.OnBrush.Color = GlobalConfig.Instance.Colors[tin.Voice]; 151 | key.Pressed = key.Dirty = true; 152 | } 153 | } 154 | } 155 | } 156 | for (int k = 0; k <= 0x7F; k++) 157 | { 158 | PianoKey key = _keys[k]; 159 | if (key.Dirty) 160 | { 161 | key.Invalidate(); 162 | } 163 | } 164 | } 165 | 166 | protected override void OnResize(EventArgs e) 167 | { 168 | SetKeySizes(); 169 | base.OnResize(e); 170 | } 171 | protected override void Dispose(bool disposing) 172 | { 173 | if (disposing) 174 | { 175 | for (int k = 0; k < 0x80; k++) 176 | { 177 | _keys[k].Dispose(); 178 | } 179 | } 180 | base.Dispose(disposing); 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /VG Music Studio/UI/Theme.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.VGMusicStudio.Properties; 2 | using Kermalis.VGMusicStudio.Util; 3 | using System; 4 | using System.Drawing; 5 | using System.Runtime.InteropServices; 6 | using System.Windows.Forms; 7 | 8 | namespace Kermalis.VGMusicStudio.UI 9 | { 10 | internal static class Theme 11 | { 12 | public static readonly Font Font = new Font("Segoe UI", 8f, FontStyle.Bold); 13 | public static readonly Color 14 | BackColor = Color.FromArgb(33, 33, 39), 15 | BackColorDisabled = Color.FromArgb(35, 42, 47), 16 | BackColorMouseOver = Color.FromArgb(32, 37, 47), 17 | BorderColor = Color.FromArgb(25, 120, 186), 18 | BorderColorDisabled = Color.FromArgb(47, 55, 60), 19 | ForeColor = Color.FromArgb(94, 159, 230), 20 | PlayerColor = Color.FromArgb(8, 8, 8), 21 | SelectionColor = Color.FromArgb(7, 51, 141), 22 | TitleBar = Color.FromArgb(16, 40, 63); 23 | 24 | public static HSLColor DrainColor(Color c) 25 | { 26 | var drained = new HSLColor(c); 27 | drained.Saturation /= 2.5; 28 | return drained; 29 | } 30 | } 31 | 32 | internal class ThemedButton : Button 33 | { 34 | public ThemedButton() : base() 35 | { 36 | FlatAppearance.MouseOverBackColor = Theme.BackColorMouseOver; 37 | FlatStyle = FlatStyle.Flat; 38 | Font = Theme.Font; 39 | ForeColor = Theme.ForeColor; 40 | } 41 | protected override void OnEnabledChanged(EventArgs e) 42 | { 43 | base.OnEnabledChanged(e); 44 | BackColor = Enabled ? Theme.BackColor : Theme.BackColorDisabled; 45 | FlatAppearance.BorderColor = Enabled ? Theme.BorderColor : Theme.BorderColorDisabled; 46 | } 47 | protected override void OnPaint(PaintEventArgs e) 48 | { 49 | base.OnPaint(e); 50 | if (!Enabled) 51 | { 52 | TextRenderer.DrawText(e.Graphics, Text, Font, ClientRectangle, Theme.DrainColor(ForeColor), BackColor); 53 | } 54 | } 55 | protected override bool ShowFocusCues => false; 56 | } 57 | internal class ThemedLabel : Label 58 | { 59 | public ThemedLabel() : base() 60 | { 61 | Font = Theme.Font; 62 | ForeColor = Theme.ForeColor; 63 | } 64 | } 65 | internal class ThemedForm : Form 66 | { 67 | public ThemedForm() : base() 68 | { 69 | BackColor = Theme.BackColor; 70 | Icon = Resources.Icon; 71 | } 72 | } 73 | internal class ThemedPanel : Panel 74 | { 75 | public ThemedPanel() : base() 76 | { 77 | SetStyle(ControlStyles.UserPaint | ControlStyles.ResizeRedraw | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint, true); 78 | } 79 | protected override void OnPaint(PaintEventArgs e) 80 | { 81 | base.OnPaint(e); 82 | using (var b = new SolidBrush(BackColor)) 83 | { 84 | e.Graphics.FillRectangle(b, e.ClipRectangle); 85 | } 86 | using (var b = new SolidBrush(Theme.BorderColor)) 87 | using (var p = new Pen(b, 2)) 88 | { 89 | e.Graphics.DrawRectangle(p, e.ClipRectangle); 90 | } 91 | } 92 | private const int WM_PAINT = 0xF; 93 | protected override void WndProc(ref Message m) 94 | { 95 | if (m.Msg == WM_PAINT) 96 | { 97 | Invalidate(); 98 | } 99 | base.WndProc(ref m); 100 | } 101 | } 102 | internal class ThemedTextBox : TextBox 103 | { 104 | public ThemedTextBox() : base() 105 | { 106 | BackColor = Theme.BackColor; 107 | Font = Theme.Font; 108 | ForeColor = Theme.ForeColor; 109 | } 110 | [DllImport("user32.dll")] 111 | private static extern IntPtr GetWindowDC(IntPtr hWnd); 112 | [DllImport("user32.dll")] 113 | private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); 114 | [DllImport("user32.dll")] 115 | private static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprc, IntPtr hrgn, uint flags); 116 | private const int WM_NCPAINT = 0x85; 117 | private const uint RDW_INVALIDATE = 0x1; 118 | private const uint RDW_IUPDATENOW = 0x100; 119 | private const uint RDW_FRAME = 0x400; 120 | protected override void WndProc(ref Message m) 121 | { 122 | base.WndProc(ref m); 123 | if (m.Msg == WM_NCPAINT && BorderStyle == BorderStyle.Fixed3D) 124 | { 125 | IntPtr hdc = GetWindowDC(Handle); 126 | using (var g = Graphics.FromHdcInternal(hdc)) 127 | using (var p = new Pen(Theme.BorderColor)) 128 | { 129 | g.DrawRectangle(p, new Rectangle(0, 0, Width - 1, Height - 1)); 130 | } 131 | ReleaseDC(Handle, hdc); 132 | } 133 | } 134 | protected override void OnSizeChanged(EventArgs e) 135 | { 136 | base.OnSizeChanged(e); 137 | RedrawWindow(Handle, IntPtr.Zero, IntPtr.Zero, RDW_FRAME | RDW_IUPDATENOW | RDW_INVALIDATE); 138 | } 139 | } 140 | internal class ThemedRichTextBox : RichTextBox 141 | { 142 | public ThemedRichTextBox() : base() 143 | { 144 | BackColor = Theme.BackColor; 145 | Font = Theme.Font; 146 | ForeColor = Theme.ForeColor; 147 | SelectionColor = Theme.SelectionColor; 148 | } 149 | } 150 | internal class ThemedNumeric : NumericUpDown 151 | { 152 | public ThemedNumeric() : base() 153 | { 154 | BackColor = Theme.BackColor; 155 | Font = new Font(Theme.Font.FontFamily, 7.5f, Theme.Font.Style); 156 | ForeColor = Theme.ForeColor; 157 | TextAlign = HorizontalAlignment.Center; 158 | } 159 | protected override void OnPaint(PaintEventArgs e) 160 | { 161 | base.OnPaint(e); 162 | ControlPaint.DrawBorder(e.Graphics, ClientRectangle, Enabled ? Theme.BorderColor : Theme.BorderColorDisabled, ButtonBorderStyle.Solid); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /VG Music Studio/UI/TrackViewer.cs: -------------------------------------------------------------------------------- 1 | using BrightIdeasSoftware; 2 | using Kermalis.VGMusicStudio.Core; 3 | using Kermalis.VGMusicStudio.Properties; 4 | using Kermalis.VGMusicStudio.Util; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.ComponentModel; 8 | using System.Drawing; 9 | using System.Linq; 10 | using System.Windows.Forms; 11 | 12 | namespace Kermalis.VGMusicStudio.UI 13 | { 14 | [DesignerCategory("")] 15 | internal class TrackViewer : ThemedForm 16 | { 17 | private List _events; 18 | private readonly ObjectListView _listView; 19 | private readonly ComboBox _tracksBox; 20 | 21 | public TrackViewer() 22 | { 23 | int w = (600 / 2) - 12 - 6, h = 400 - 12 - 11; 24 | _listView = new ObjectListView 25 | { 26 | FullRowSelect = true, 27 | HeaderStyle = ColumnHeaderStyle.Nonclickable, 28 | HideSelection = false, 29 | Location = new Point(12, 12), 30 | MultiSelect = false, 31 | RowFormatter = RowColorer, 32 | ShowGroups = false, 33 | Size = new Size(w, h), 34 | UseFiltering = true, 35 | UseFilterIndicator = true 36 | }; 37 | OLVColumn c1, c2, c3, c4; 38 | c1 = new OLVColumn(Strings.TrackViewerEvent, "Command.Label"); 39 | c2 = new OLVColumn(Strings.TrackViewerArguments, "Command.Arguments") { UseFiltering = false }; 40 | c3 = new OLVColumn(Strings.TrackViewerOffset, "Offset") { AspectToStringFormat = "0x{0:X}", UseFiltering = false }; 41 | c4 = new OLVColumn(Strings.TrackViewerTicks, "Ticks") { AspectGetter = (o) => string.Join(", ", ((SongEvent)o).Ticks), UseFiltering = false }; 42 | c1.Width = c2.Width = c3.Width = 72; 43 | c4.Width = 47; 44 | c1.Hideable = c2.Hideable = c3.Hideable = c4.Hideable = false; 45 | c1.TextAlign = c2.TextAlign = c3.TextAlign = c4.TextAlign = HorizontalAlignment.Center; 46 | _listView.AllColumns.AddRange(new OLVColumn[] { c1, c2, c3, c4 }); 47 | _listView.RebuildColumns(); 48 | _listView.ItemActivate += ListView_ItemActivate; 49 | 50 | var panel1 = new ThemedPanel { Location = new Point(306, 12), Size = new Size(w, h) }; 51 | _tracksBox = new ComboBox 52 | { 53 | Enabled = false, 54 | Location = new Point(4, 4), 55 | Size = new Size(100, 21) 56 | }; 57 | _tracksBox.SelectedIndexChanged += TracksBox_SelectedIndexChanged; 58 | panel1.Controls.AddRange(new Control[] { _tracksBox }); 59 | 60 | ClientSize = new Size(600, 400); 61 | Controls.AddRange(new Control[] { _listView, panel1 }); 62 | FormBorderStyle = FormBorderStyle.FixedDialog; 63 | MaximizeBox = false; 64 | Text = $"{Utils.ProgramName} ― {Strings.TrackViewerTitle}"; 65 | 66 | UpdateTracks(); 67 | } 68 | 69 | private void ListView_ItemActivate(object sender, EventArgs e) 70 | { 71 | List list = ((SongEvent)_listView.SelectedItem.RowObject).Ticks; 72 | if (list.Count > 0) 73 | { 74 | Engine.Instance?.Player.SetCurrentPosition(list[0]); 75 | MainForm.Instance.LetUIKnowPlayerIsPlaying(); 76 | } 77 | } 78 | 79 | private void RowColorer(OLVListItem item) 80 | { 81 | item.BackColor = ((SongEvent)item.RowObject).Command.Color; 82 | } 83 | 84 | private void TracksBox_SelectedIndexChanged(object sender, EventArgs e) 85 | { 86 | int i = _tracksBox.SelectedIndex; 87 | if (i != -1) 88 | { 89 | _events = Engine.Instance.Player.Events[i]; 90 | _listView.SetObjects(_events); 91 | } 92 | else 93 | { 94 | _listView.Items.Clear(); 95 | } 96 | } 97 | public void UpdateTracks() 98 | { 99 | int numTracks = (Engine.Instance?.Player.Events?.Length).GetValueOrDefault(); 100 | bool tracks = numTracks > 0; 101 | _tracksBox.Enabled = tracks; 102 | if (tracks) 103 | { 104 | // Track 0, Track 1, ... 105 | _tracksBox.DataSource = Enumerable.Range(0, numTracks).Select(i => string.Format(Strings.TrackViewerTrackX, i)).ToList(); 106 | } 107 | else 108 | { 109 | _tracksBox.DataSource = null; 110 | } 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /VG Music Studio/UI/ValueTextBox.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.VGMusicStudio.Util; 2 | using System; 3 | using System.Windows.Forms; 4 | 5 | namespace Kermalis.VGMusicStudio.UI 6 | { 7 | internal class ValueTextBox : ThemedTextBox 8 | { 9 | private bool _hex = false; 10 | public bool Hexadecimal 11 | { 12 | get => _hex; 13 | set 14 | { 15 | _hex = value; 16 | OnTextChanged(EventArgs.Empty); 17 | SelectionStart = Text.Length; 18 | } 19 | } 20 | private long _max = long.MaxValue; 21 | public long Maximum 22 | { 23 | get => _max; 24 | set 25 | { 26 | _max = value; 27 | OnTextChanged(EventArgs.Empty); 28 | } 29 | } 30 | private long _min = 0; 31 | public long Minimum 32 | { 33 | get => _min; 34 | set 35 | { 36 | _min = value; 37 | OnTextChanged(EventArgs.Empty); 38 | } 39 | } 40 | public long Value 41 | { 42 | get 43 | { 44 | if (TextLength > 0) 45 | { 46 | if (Utils.TryParseValue(Text, _min, _max, out long l)) 47 | { 48 | return l; 49 | } 50 | } 51 | return _min; 52 | } 53 | set 54 | { 55 | int i = SelectionStart; 56 | Text = Hexadecimal ? ("0x" + value.ToString("X")) : value.ToString(); 57 | SelectionStart = i; 58 | OnValueChanged(EventArgs.Empty); 59 | } 60 | } 61 | 62 | protected override void WndProc(ref Message m) 63 | { 64 | const int WM_NOTIFY = 0x0282; 65 | if (m.Msg == WM_NOTIFY && m.WParam == new IntPtr(0xB)) 66 | { 67 | if (Hexadecimal && SelectionStart < 2) 68 | { 69 | SelectionStart = 2; 70 | } 71 | } 72 | base.WndProc(ref m); 73 | } 74 | protected override void OnKeyPress(KeyPressEventArgs e) 75 | { 76 | e.Handled = true; // Don't pay attention to this event unless: 77 | 78 | if ((char.IsControl(e.KeyChar) && !(Hexadecimal && SelectionStart <= 2 && SelectionLength == 0 && e.KeyChar == (char)Keys.Back)) || // Backspace isn't used on the "0x" prefix 79 | char.IsDigit(e.KeyChar) || // It is a digit 80 | (e.KeyChar >= 'a' && e.KeyChar <= 'f') || // It is a letter that shows in hex 81 | (e.KeyChar >= 'A' && e.KeyChar <= 'F')) 82 | { 83 | e.Handled = false; 84 | } 85 | base.OnKeyPress(e); 86 | } 87 | protected override void OnTextChanged(EventArgs e) 88 | { 89 | base.OnTextChanged(e); 90 | Value = Value; 91 | } 92 | 93 | private EventHandler _onValueChanged = null; 94 | public event EventHandler ValueChanged 95 | { 96 | add => _onValueChanged += value; 97 | remove => _onValueChanged -= value; 98 | } 99 | protected virtual void OnValueChanged(EventArgs e) 100 | { 101 | _onValueChanged?.Invoke(this, e); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /VG Music Studio/Util/BetterExceptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Kermalis.VGMusicStudio.Util 5 | { 6 | internal class InvalidValueException : Exception 7 | { 8 | public object Value { get; } 9 | 10 | public InvalidValueException(object value, string message) : base(message) 11 | { 12 | Value = value; 13 | } 14 | } 15 | internal class BetterKeyNotFoundException : KeyNotFoundException 16 | { 17 | public object Key { get; } 18 | 19 | public BetterKeyNotFoundException(object key, Exception innerException) : base($"\"{key}\" was not present in the dictionary.", innerException) 20 | { 21 | Key = key; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /VG Music Studio/Util/HSLColor.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | 3 | namespace Kermalis.VGMusicStudio.Util 4 | { 5 | // https://richnewman.wordpress.com/about/code-listings-and-diagrams/hslcolor-class/ 6 | class HSLColor 7 | { 8 | // Private data members below are on scale 0-1 9 | // They are scaled for use externally based on scale 10 | private double hue = 1.0; 11 | private double saturation = 1.0; 12 | private double luminosity = 1.0; 13 | 14 | private const double scale = 240.0; 15 | 16 | public double Hue 17 | { 18 | get { return hue * scale; } 19 | set { hue = CheckRange(value / scale); } 20 | } 21 | public double Saturation 22 | { 23 | get { return saturation * scale; } 24 | set { saturation = CheckRange(value / scale); } 25 | } 26 | public double Luminosity 27 | { 28 | get { return luminosity * scale; } 29 | set { luminosity = CheckRange(value / scale); } 30 | } 31 | 32 | private double CheckRange(double value) 33 | { 34 | if (value < 0.0) 35 | { 36 | value = 0.0; 37 | } 38 | else if (value > 1.0) 39 | { 40 | value = 1.0; 41 | } 42 | return value; 43 | } 44 | 45 | public override string ToString() 46 | { 47 | return string.Format("H: {0:#0.##} S: {1:#0.##} L: {2:#0.##}", Hue, Saturation, Luminosity); 48 | } 49 | 50 | public string ToRGBString() 51 | { 52 | Color color = this; 53 | return string.Format("R: {0:#0.##} G: {1:#0.##} B: {2:#0.##}", color.R, color.G, color.B); 54 | } 55 | 56 | #region Casts to/from System.Drawing.Color 57 | public static implicit operator Color(HSLColor hslColor) 58 | { 59 | double r = 0, g = 0, b = 0; 60 | if (hslColor.luminosity != 0) 61 | { 62 | if (hslColor.saturation == 0) 63 | { 64 | r = g = b = hslColor.luminosity; 65 | } 66 | else 67 | { 68 | double temp2 = GetTemp2(hslColor); 69 | double temp1 = 2.0 * hslColor.luminosity - temp2; 70 | 71 | r = GetColorComponent(temp1, temp2, hslColor.hue + 1.0 / 3.0); 72 | g = GetColorComponent(temp1, temp2, hslColor.hue); 73 | b = GetColorComponent(temp1, temp2, hslColor.hue - 1.0 / 3.0); 74 | } 75 | } 76 | return Color.FromArgb((int)(255 * r), (int)(255 * g), (int)(255 * b)); 77 | } 78 | 79 | private static double GetColorComponent(double temp1, double temp2, double temp3) 80 | { 81 | temp3 = MoveIntoRange(temp3); 82 | if (temp3 < 1.0 / 6.0) 83 | { 84 | return temp1 + (temp2 - temp1) * 6.0 * temp3; 85 | } 86 | else if (temp3 < 0.5) 87 | { 88 | return temp2; 89 | } 90 | else if (temp3 < 2.0 / 3.0) 91 | { 92 | return temp1 + ((temp2 - temp1) * ((2.0 / 3.0) - temp3) * 6.0); 93 | } 94 | else 95 | { 96 | return temp1; 97 | } 98 | } 99 | private static double MoveIntoRange(double temp3) 100 | { 101 | if (temp3 < 0.0) 102 | { 103 | temp3 += 1.0; 104 | } 105 | else if (temp3 > 1.0) 106 | { 107 | temp3 -= 1.0; 108 | } 109 | return temp3; 110 | } 111 | private static double GetTemp2(HSLColor hslColor) 112 | { 113 | double temp2; 114 | if (hslColor.luminosity < 0.5) //<=?? 115 | { 116 | temp2 = hslColor.luminosity * (1.0 + hslColor.saturation); 117 | } 118 | else 119 | { 120 | temp2 = hslColor.luminosity + hslColor.saturation - (hslColor.luminosity * hslColor.saturation); 121 | } 122 | return temp2; 123 | } 124 | 125 | public static implicit operator HSLColor(Color color) 126 | { 127 | HSLColor hslColor = new HSLColor 128 | { 129 | hue = color.GetHue() / 360.0, // We store hue as 0-1 as opposed to 0-360 130 | luminosity = color.GetBrightness(), 131 | saturation = color.GetSaturation() 132 | }; 133 | return hslColor; 134 | } 135 | #endregion 136 | 137 | public void SetRGB(int red, int green, int blue) 138 | { 139 | HSLColor hslColor = Color.FromArgb(red, green, blue); 140 | hue = hslColor.hue; 141 | saturation = hslColor.saturation; 142 | luminosity = hslColor.luminosity; 143 | } 144 | 145 | public HSLColor() { } 146 | public HSLColor(Color color) 147 | { 148 | SetRGB(color.R, color.G, color.B); 149 | } 150 | public HSLColor(int red, int green, int blue) 151 | { 152 | SetRGB(red, green, blue); 153 | } 154 | public HSLColor(double hue, double saturation, double luminosity) 155 | { 156 | Hue = hue; 157 | Saturation = saturation; 158 | Luminosity = luminosity; 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /VG Music Studio/Util/SampleUtils.cs: -------------------------------------------------------------------------------- 1 | namespace Kermalis.VGMusicStudio.Util 2 | { 3 | internal static class SampleUtils 4 | { 5 | public static short[] PCMU8ToPCM16(byte[] data, int index, int length) 6 | { 7 | short[] ret = new short[length]; 8 | for (int i = 0; i < length; i++) 9 | { 10 | byte b = data[index + i]; 11 | ret[i] = (short)((b - 0x80) << 8); 12 | } 13 | return ret; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /VG Music Studio/Util/TimeBarrier.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Threading; 3 | 4 | namespace Kermalis.VGMusicStudio.Util 5 | { 6 | // Credit to ipatix 7 | internal class TimeBarrier 8 | { 9 | private readonly Stopwatch _sw; 10 | private readonly double _timerInterval; 11 | private readonly double _waitInterval; 12 | private double _lastTimeStamp; 13 | private bool _started; 14 | 15 | public TimeBarrier(double framesPerSecond) 16 | { 17 | _waitInterval = 1.0 / framesPerSecond; 18 | _started = false; 19 | _sw = new Stopwatch(); 20 | _timerInterval = 1.0 / Stopwatch.Frequency; 21 | } 22 | 23 | public void Wait() 24 | { 25 | if (!_started) 26 | { 27 | return; 28 | } 29 | double totalElapsed = _sw.ElapsedTicks * _timerInterval; 30 | double desiredTimeStamp = _lastTimeStamp + _waitInterval; 31 | double timeToWait = desiredTimeStamp - totalElapsed; 32 | if (timeToWait > 0) 33 | { 34 | Thread.Sleep((int)(timeToWait * 1000)); 35 | } 36 | _lastTimeStamp = desiredTimeStamp; 37 | } 38 | 39 | public void Start() 40 | { 41 | if (_started) 42 | { 43 | return; 44 | } 45 | _started = true; 46 | _lastTimeStamp = 0; 47 | _sw.Restart(); 48 | } 49 | 50 | public void Stop() 51 | { 52 | if (!_started) 53 | { 54 | return; 55 | } 56 | _started = false; 57 | _sw.Stop(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /VG Music Studio/Util/Utils.cs: -------------------------------------------------------------------------------- 1 | using Kermalis.VGMusicStudio.Core; 2 | using Kermalis.VGMusicStudio.Properties; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Globalization; 6 | using System.IO; 7 | using YamlDotNet.RepresentationModel; 8 | 9 | namespace Kermalis.VGMusicStudio.Util 10 | { 11 | internal static class Utils 12 | { 13 | public const string ProgramName = "VG Music Studio"; 14 | 15 | private static readonly Random _rng = new Random(); 16 | private static readonly string[] _notes = Strings.Notes.Split(';'); 17 | private static readonly char[] _spaceArray = new char[1] { ' ' }; 18 | 19 | public static bool TryParseValue(string value, long minValue, long maxValue, out long outValue) 20 | { 21 | try 22 | { 23 | outValue = ParseValue(string.Empty, value, minValue, maxValue); 24 | return true; 25 | } 26 | catch 27 | { 28 | outValue = default; 29 | return false; 30 | } 31 | } 32 | /// 33 | public static long ParseValue(string valueName, string value, long minValue, long maxValue) 34 | { 35 | string GetMessage() 36 | { 37 | return string.Format(Strings.ErrorValueParseRanged, valueName, minValue, maxValue); 38 | } 39 | 40 | var provider = new CultureInfo("en-US"); 41 | if (value.StartsWith("0x") && long.TryParse(value.Substring(2), NumberStyles.HexNumber, provider, out long hexp)) 42 | { 43 | if (hexp < minValue || hexp > maxValue) 44 | { 45 | throw new InvalidValueException(hexp, GetMessage()); 46 | } 47 | return hexp; 48 | } 49 | else if (long.TryParse(value, NumberStyles.Integer, provider, out long dec)) 50 | { 51 | if (dec < minValue || dec > maxValue) 52 | { 53 | throw new InvalidValueException(dec, GetMessage()); 54 | } 55 | return dec; 56 | } 57 | else if (long.TryParse(value, NumberStyles.HexNumber, provider, out long hex)) 58 | { 59 | if (hex < minValue || hex > maxValue) 60 | { 61 | throw new InvalidValueException(hex, GetMessage()); 62 | } 63 | return hex; 64 | } 65 | throw new InvalidValueException(value, string.Format(Strings.ErrorValueParse, valueName)); 66 | } 67 | /// 68 | public static bool ParseBoolean(string valueName, string value) 69 | { 70 | if (!bool.TryParse(value, out bool result)) 71 | { 72 | throw new InvalidValueException(value, string.Format(Strings.ErrorBoolParse, valueName)); 73 | } 74 | return result; 75 | } 76 | /// 77 | public static TEnum ParseEnum(string valueName, string value) where TEnum : struct 78 | { 79 | if (!Enum.TryParse(value, out TEnum result)) 80 | { 81 | throw new InvalidValueException(value, string.Format(Strings.ErrorConfigKeyInvalid, valueName)); 82 | } 83 | return result; 84 | } 85 | /// 86 | public static TValue GetValue(this IDictionary dictionary, TKey key) 87 | { 88 | try 89 | { 90 | return dictionary[key]; 91 | } 92 | catch (KeyNotFoundException ex) 93 | { 94 | throw new BetterKeyNotFoundException(key, ex.InnerException); 95 | } 96 | } 97 | /// 98 | /// 99 | public static long GetValidValue(this YamlMappingNode yamlNode, string key, long minRange, long maxRange) 100 | { 101 | return ParseValue(key, yamlNode.Children.GetValue(key).ToString(), minRange, maxRange); 102 | } 103 | /// 104 | /// 105 | public static bool GetValidBoolean(this YamlMappingNode yamlNode, string key) 106 | { 107 | return ParseBoolean(key, yamlNode.Children.GetValue(key).ToString()); 108 | } 109 | /// 110 | /// 111 | public static TEnum GetValidEnum(this YamlMappingNode yamlNode, string key) where TEnum : struct 112 | { 113 | return ParseEnum(key, yamlNode.Children.GetValue(key).ToString()); 114 | } 115 | public static string[] SplitSpace(this string str, StringSplitOptions options) 116 | { 117 | return str.Split(_spaceArray, options); 118 | } 119 | 120 | public static string Print(this IEnumerable source, bool parenthesis = true) 121 | { 122 | string str = parenthesis ? "( " : ""; 123 | str += string.Join(", ", source); 124 | str += parenthesis ? " )" : ""; 125 | return str; 126 | } 127 | /// Fisher-Yates Shuffle 128 | public static void Shuffle(this IList source) 129 | { 130 | for (int a = 0; a < source.Count - 1; a++) 131 | { 132 | int b = _rng.Next(a, source.Count); 133 | T value = source[a]; 134 | source[a] = source[b]; 135 | source[b] = value; 136 | } 137 | } 138 | 139 | public static string GetPianoKeyName(int key) 140 | { 141 | return _notes[key]; 142 | } 143 | public static string GetNoteName(int key) 144 | { 145 | return _notes[key % 12] + ((key / 12) + (GlobalConfig.Instance.MiddleCOctave - 5)); 146 | } 147 | 148 | public static string CombineWithBaseDirectory(string path) 149 | { 150 | return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /VG Music Studio/midi2agb.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kermalis/VGMusicStudio/d14a38e264eeb1392289eeb2bd5b450c92877949/VG Music Studio/midi2agb.exe -------------------------------------------------------------------------------- /VG Music Studio/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | --------------------------------------------------------------------------------