Skip to content

Commit ba28bfd

Browse files
committed
[AlphaDream] Add exporting to DLS
1 parent f58b9b9 commit ba28bfd

File tree

12 files changed

+299
-17
lines changed

12 files changed

+299
-17
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,12 @@ If you want to talk or would like a game added to our configs, join our [Discord
7373

7474
----
7575
## VG Music Studio Uses:
76+
* [DLS2](https://github.com/Kermalis/DLS2)
77+
* [EndianBinaryIO](https://github.com/Kermalis/EndianBinaryIO)
7678
* [NAudio](https://github.com/naudio/NAudio)
7779
* [ObjectListView](http://objectlistview.sourceforge.net)
78-
* [YamlDotNet](https://github.com/aaubry/YamlDotNet/wiki)
79-
* [My EndianBinaryIO library](https://github.com/Kermalis/EndianBinaryIO)
80-
* [My SoundFont2 library](https://github.com/Kermalis/SoundFont2)
8180
* [My fork of Sanford.Multimedia.Midi](https://github.com/Kermalis/Sanford.Multimedia.Midi)
81+
* [SoundFont2](https://github.com/Kermalis/SoundFont2)
82+
* [YamlDotNet](https://github.com/aaubry/YamlDotNet/wiki)
8283

8384
[Discord]: https://discord.gg/mBQXCTs
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
using Kermalis.DLS2;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
5+
namespace Kermalis.VGMusicStudio.Core.GBA.AlphaDream
6+
{
7+
internal sealed class SoundFontSaver_DLS
8+
{
9+
// Since every key will use the same articulation data, just store one instance
10+
private static readonly Level2ArticulatorChunk _art2 = new Level2ArticulatorChunk()
11+
{
12+
new Level2ArticulatorConnectionBlock() { Destination = Level2ArticulatorDestination.LFOFrequency, Scale = 2786 },
13+
new Level2ArticulatorConnectionBlock() { Destination = Level2ArticulatorDestination.VIBFrequency, Scale = 2786 },
14+
new Level2ArticulatorConnectionBlock() { Source = Level2ArticulatorSource.KeyNumber, Destination = Level2ArticulatorDestination.Pitch },
15+
new Level2ArticulatorConnectionBlock() { Source = Level2ArticulatorSource.Vibrato, Control = Level2ArticulatorSource.Modulation_CC1, Destination = Level2ArticulatorDestination.Pitch, BipolarSource = true, Scale = 0x320000 },
16+
new Level2ArticulatorConnectionBlock() { Source = Level2ArticulatorSource.Vibrato, Control = Level2ArticulatorSource.ChannelPressure, Destination = Level2ArticulatorDestination.Pitch, BipolarSource = true, Scale = 0x320000 },
17+
new Level2ArticulatorConnectionBlock() { Source = Level2ArticulatorSource.Pan_CC10, Destination = Level2ArticulatorDestination.Pan, BipolarSource = true, Scale = 0xFE0000 },
18+
new Level2ArticulatorConnectionBlock() { Source = Level2ArticulatorSource.ChorusSend_CC91, Destination = Level2ArticulatorDestination.Reverb, Scale = 0xC80000 },
19+
new Level2ArticulatorConnectionBlock() { Source = Level2ArticulatorSource.Reverb_SendCC93, Destination = Level2ArticulatorDestination.Chorus, Scale = 0xC80000 }
20+
};
21+
22+
public static void Save(Config config, string path)
23+
{
24+
var dls = new DLS();
25+
AddInfo(config, dls);
26+
Dictionary<int, (WaveSampleChunk, int)> sampleDict = AddSamples(config, dls);
27+
AddInstruments(config, dls, sampleDict);
28+
dls.Save(path);
29+
}
30+
31+
private static void AddInfo(Config config, DLS dls)
32+
{
33+
var info = new ListChunk("INFO");
34+
dls.Add(info);
35+
info.Add(new InfoSubChunk("INAM", config.Name));
36+
//info.Add(new InfoSubChunk("ICOP", config.Creator));
37+
info.Add(new InfoSubChunk("IENG", "Kermalis"));
38+
info.Add(new InfoSubChunk("ISFT", Util.Utils.ProgramName));
39+
}
40+
41+
private static Dictionary<int, (WaveSampleChunk, int)> AddSamples(Config config, DLS dls)
42+
{
43+
ListChunk waves = dls.WavePool;
44+
var sampleDict = new Dictionary<int, (WaveSampleChunk, int)>((int)config.SampleTableSize);
45+
for (int i = 0; i < config.SampleTableSize; i++)
46+
{
47+
int ofs = config.Reader.ReadInt32(config.SampleTableOffset + (i * 4));
48+
if (ofs == 0)
49+
{
50+
continue; // Skip null samples
51+
}
52+
53+
ofs += config.SampleTableOffset;
54+
SampleHeader sh = config.Reader.ReadObject<SampleHeader>(ofs);
55+
56+
// Create format chunk
57+
var fmt = new FormatChunk(WaveFormat.PCM);
58+
fmt.WaveInfo.Channels = 1;
59+
fmt.WaveInfo.SamplesPerSec = (uint)(sh.SampleRate >> 10);
60+
fmt.WaveInfo.AvgBytesPerSec = fmt.WaveInfo.SamplesPerSec;
61+
fmt.WaveInfo.BlockAlign = 1;
62+
fmt.FormatInfo.BitsPerSample = 8;
63+
// Create wave sample chunk and add loop if there is one
64+
var wsmp = new WaveSampleChunk()
65+
{
66+
UnityNote = 60,
67+
Options = WaveSampleOptions.NoTruncation | WaveSampleOptions.NoCompression
68+
};
69+
if (sh.DoesLoop == 0x40000000)
70+
{
71+
wsmp.Loop = new WaveSampleLoop
72+
{
73+
LoopStart = (uint)sh.LoopOffset,
74+
LoopLength = (uint)(sh.Length - sh.LoopOffset),
75+
LoopType = LoopType.Forward
76+
};
77+
}
78+
// Get PCM sample
79+
byte[] pcm = new byte[sh.Length];
80+
System.Array.Copy(config.ROM, ofs + 0x10, pcm, 0, sh.Length);
81+
82+
// Add
83+
int dlsIndex = waves.Count;
84+
waves.Add(new ListChunk("wave")
85+
{
86+
fmt,
87+
wsmp,
88+
new DataChunk(pcm),
89+
new ListChunk("INFO")
90+
{
91+
new InfoSubChunk("INAM", $"Sample {i}")
92+
}
93+
});
94+
sampleDict.Add(i, (wsmp, dlsIndex));
95+
}
96+
return sampleDict;
97+
}
98+
99+
private static void AddInstruments(Config config, DLS dls, Dictionary<int, (WaveSampleChunk, int)> sampleDict)
100+
{
101+
ListChunk lins = dls.InstrumentList;
102+
for (int v = 0; v < 256; v++)
103+
{
104+
short off = config.Reader.ReadInt16(config.VoiceTableOffset + (v * 2));
105+
short nextOff = config.Reader.ReadInt16(config.VoiceTableOffset + ((v + 1) * 2));
106+
int numEntries = (nextOff - off) / 8; // Each entry is 8 bytes
107+
if (numEntries == 0)
108+
{
109+
continue; // Skip empty entries
110+
}
111+
112+
var ins = new ListChunk("ins ");
113+
ins.Add(new InstrumentHeaderChunk
114+
{
115+
NumRegions = (uint)numEntries,
116+
Locale = new MIDILocale(0, (byte)(v / 128), false, (byte)(v % 128))
117+
});
118+
var lrgn = new ListChunk("lrgn");
119+
ins.Add(lrgn);
120+
ins.Add(new ListChunk("INFO")
121+
{
122+
new InfoSubChunk("INAM", $"Instrument {v}")
123+
});
124+
lins.Add(ins);
125+
for (int e = 0; e < numEntries; e++)
126+
{
127+
VoiceEntry entry = config.Reader.ReadObject<VoiceEntry>(config.VoiceTableOffset + off + (e * 8));
128+
// Sample
129+
if (entry.Sample >= config.SampleTableSize)
130+
{
131+
Debug.WriteLine(string.Format("Voice {0} uses an invalid sample id ({1})", v, entry.Sample));
132+
continue;
133+
}
134+
if (!sampleDict.TryGetValue(entry.Sample, out (WaveSampleChunk, int) value))
135+
{
136+
Debug.WriteLine(string.Format("Voice {0} uses a null sample id ({1})", v, entry.Sample));
137+
continue;
138+
}
139+
void Add(ushort low, ushort high, ushort baseKey)
140+
{
141+
var rgnh = new RegionHeaderChunk();
142+
rgnh.KeyRange.Low = low;
143+
rgnh.KeyRange.High = high;
144+
lrgn.Add(new ListChunk("rgn2")
145+
{
146+
rgnh,
147+
new WaveSampleChunk()
148+
{
149+
UnityNote = baseKey,
150+
Options = WaveSampleOptions.NoTruncation | WaveSampleOptions.NoCompression,
151+
Loop = value.Item1.Loop
152+
},
153+
new WaveLinkChunk()
154+
{
155+
Channels = WaveLinkChannels.Left,
156+
TableIndex = (uint)value.Item2
157+
},
158+
new ListChunk("lar2")
159+
{
160+
_art2
161+
}
162+
});
163+
}
164+
// Fixed frequency - Since DLS does not support it, we need to manually add every key with its own base note
165+
if (entry.IsFixedFrequency == 0x80)
166+
{
167+
for (ushort i = entry.MinKey; i <= entry.MaxKey; i++)
168+
{
169+
Add(i, i, i);
170+
}
171+
}
172+
else
173+
{
174+
Add(entry.MinKey, entry.MaxKey, 60);
175+
}
176+
}
177+
}
178+
}
179+
}
180+
}

VG Music Studio/Core/GBA/AlphaDream/SoundFontSaver.cs renamed to VG Music Studio/Core/GBA/AlphaDream/SoundFontSaver_SF2.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
using Kermalis.SoundFont2;
22
using Kermalis.VGMusicStudio.Util;
33
using System.Collections.Generic;
4+
using System.Diagnostics;
45

56
namespace Kermalis.VGMusicStudio.Core.GBA.AlphaDream
67
{
7-
internal sealed class SoundFontSaver
8+
internal sealed class SoundFontSaver_SF2
89
{
910
public static void Save(Config config, string path)
1011
{
@@ -19,7 +20,7 @@ private static void AddInfo(Config config, SF2 sf2)
1920
{
2021
sf2.InfoChunk.Bank = config.Name;
2122
//sf2.InfoChunk.Copyright = config.Creator;
22-
sf2.InfoChunk.Tools = "VG Music Studio by Kermalis";
23+
sf2.InfoChunk.Tools = Util.Utils.ProgramName + " by Kermalis";
2324
}
2425

2526
private static Dictionary<int, (SampleHeader, int)> AddSamples(Config config, SF2 sf2)
@@ -77,17 +78,17 @@ private static void AddInstruments(Config config, SF2 sf2, Dictionary<int, (Samp
7778
{
7879
if (!sampleDict.TryGetValue(entry.Sample, out (SampleHeader, int) value))
7980
{
80-
System.Diagnostics.Debug.WriteLine(string.Format("Voice {0} uses a null sample id ({1})", v, entry.Sample));
81+
Debug.WriteLine(string.Format("Voice {0} uses a null sample id ({1})", v, entry.Sample));
8182
}
8283
else
8384
{
8485
sf2.AddInstrumentGenerator(SF2Generator.SampleModes, new SF2GeneratorAmount { Amount = (short)(value.Item1.DoesLoop == 0x40000000 ? 1 : 0) });
85-
sf2.AddInstrumentGenerator(SF2Generator.SampleID, new SF2GeneratorAmount { Amount = (short)value.Item2 });
86+
sf2.AddInstrumentGenerator(SF2Generator.SampleID, new SF2GeneratorAmount { UAmount = (ushort)value.Item2 });
8687
}
8788
}
8889
else
8990
{
90-
System.Diagnostics.Debug.WriteLine(string.Format("Voice {0} uses an invalid sample id ({1})", v, entry.Sample));
91+
Debug.WriteLine(string.Format("Voice {0} uses an invalid sample id ({1})", v, entry.Sample));
9192
}
9293
}
9394
}

VG Music Studio/Core/GBA/MP2K/Player.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Sanford.Multimedia.Midi;
44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.Linq;
78
using System.Threading;
89

@@ -51,7 +52,7 @@ private void CreateThread()
5152
}
5253
private void WaitThread()
5354
{
54-
if (_thread != null && (_thread.ThreadState == ThreadState.Running || _thread.ThreadState == ThreadState.WaitSleepJoin))
55+
if (_thread != null && (_thread.ThreadState == System.Threading.ThreadState.Running || _thread.ThreadState == System.Threading.ThreadState.WaitSleepJoin))
5556
{
5657
_thread.Join();
5758
}
@@ -646,7 +647,7 @@ public void SaveAsMIDI(string fileName, MIDISaveArgs args)
646647
if (args.ReverseVolume)
647648
{
648649
baseVolume = Events.SelectMany(e => e).Where(e => e.Command is VolumeCommand).Select(e => ((VolumeCommand)e.Command).Volume).Max();
649-
System.Diagnostics.Debug.WriteLine($"Reversing volume back from {baseVolume}.");
650+
Debug.WriteLine($"Reversing volume back from {baseVolume}.");
650651
}
651652

652653
using (var midi = new Sequence(24) { Format = 1 })

VG Music Studio/Core/Debug.cs renamed to VG Music Studio/Core/VGMSDebug.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
namespace Kermalis.VGMusicStudio.Core
99
{
1010
#if DEBUG
11-
internal static class Debug
11+
internal static class VGMSDebug
1212
{
1313
public static void MIDIVolumeMerger(string f1, string f2)
1414
{
35.5 KB
Binary file not shown.

VG Music Studio/Properties/Strings.Designer.cs

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

VG Music Studio/Properties/Strings.es.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,4 +333,16 @@
333333
<data name="SuccessSaveSF2" xml:space="preserve">
334334
<value>SF2 guardado en {0}.</value>
335335
</data>
336+
<data name="ErrorSaveDLS" xml:space="preserve">
337+
<value>Error al Exportar DLS</value>
338+
</data>
339+
<data name="FilterSaveDLS" xml:space="preserve">
340+
<value>Archivos DLS</value>
341+
</data>
342+
<data name="MenuSaveDLS" xml:space="preserve">
343+
<value>Exportar VoiceTable como DLS</value>
344+
</data>
345+
<data name="SuccessSaveDLS" xml:space="preserve">
346+
<value>DLS guardado en {0}.</value>
347+
</data>
336348
</root>

VG Music Studio/Properties/Strings.it.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,4 +333,16 @@
333333
<data name="SuccessSaveSF2" xml:space="preserve">
334334
<value>VoiceTable salvata in {0}.</value>
335335
</data>
336+
<data name="ErrorSaveDLS" xml:space="preserve">
337+
<value>Errore Durante L'Esportazione in DLS</value>
338+
</data>
339+
<data name="FilterSaveDLS" xml:space="preserve">
340+
<value>File DLS</value>
341+
</data>
342+
<data name="MenuSaveDLS" xml:space="preserve">
343+
<value>Esporta VoiceTable In DLS</value>
344+
</data>
345+
<data name="SuccessSaveDLS" xml:space="preserve">
346+
<value>VoiceTable salvata in {0}.</value>
347+
</data>
336348
</root>

VG Music Studio/Properties/Strings.resx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,4 +360,17 @@
360360
<value>VoiceTable saved to {0}.</value>
361361
<comment>{0} is the file name.</comment>
362362
</data>
363+
<data name="ErrorSaveDLS" xml:space="preserve">
364+
<value>Error Exporting DLS</value>
365+
</data>
366+
<data name="FilterSaveDLS" xml:space="preserve">
367+
<value>DLS Files</value>
368+
</data>
369+
<data name="MenuSaveDLS" xml:space="preserve">
370+
<value>Export VoiceTable as DLS</value>
371+
</data>
372+
<data name="SuccessSaveDLS" xml:space="preserve">
373+
<value>VoiceTable saved to {0}.</value>
374+
<comment>{0} is the file name.</comment>
375+
</data>
363376
</root>

0 commit comments

Comments
 (0)