├── .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 | [][Discord]
4 | [](https://github.com/Kermalis/VGMusicStudio/releases/latest)
5 | [](https://github.com/Kermalis/VGMusicStudio/releases/latest)
6 | [](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 | [](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 |
--------------------------------------------------------------------------------