@@ -0,0 +1,206 @@ |
|
|
1 |
unit DMFImport; |
|
|
2 |
|
|
|
3 |
{$mode objfpc}{$H+} |
|
|
4 |
|
|
|
5 |
interface |
|
|
6 |
|
|
|
7 |
uses |
|
|
8 |
Classes, SysUtils, ZStream, Song, hUGEDataTypes; |
|
|
9 |
|
|
|
10 |
type |
|
|
11 |
EDMFException = class(Exception); |
|
|
12 |
|
|
|
13 |
function LoadSongFromDmfStream(Stream: TStream): TSong; |
|
|
14 |
|
|
|
15 |
implementation |
|
|
16 |
|
|
|
17 |
type |
|
|
18 |
|
|
|
19 |
{ TDMFStream } |
|
|
20 |
|
|
|
21 |
TDMFStream = class(Tdecompressionstream) |
|
|
22 |
function ReadDMFString: String; |
|
|
23 |
end; |
|
|
24 |
|
|
|
25 |
function LoadSongFromDmfStream(Stream: TStream): TSong; |
|
|
26 |
var |
|
|
27 |
DS: TDMFStream; |
|
|
28 |
I, J, Row: Integer; |
|
|
29 |
TotalInstruments, TotalWavetables: Integer; |
|
|
30 |
Temp: QWord; |
|
|
31 |
TempS: String; |
|
|
32 |
Pat: PPattern; |
|
|
33 |
begin |
|
|
34 |
InitializeSong(Result); |
|
|
35 |
|
|
|
36 |
DS := TDMFStream.create(Stream); |
|
|
37 |
|
|
|
38 |
//FORMAT FLAGS |
|
|
39 |
// 16 Bytes: Format String, must be ".DelekDefleMask." |
|
|
40 |
DS.Seek(16, soCurrent); |
|
|
41 |
// 1 Byte: File Version, must be 24 (0x18) for DefleMask v0.12.0 |
|
|
42 |
Temp := DS.ReadByte; |
|
|
43 |
Writeln(temp); |
|
|
44 |
if Temp <> 24 then |
|
|
45 |
raise EDMFException.Create('DMF file was invalid version!'); |
|
|
46 |
|
|
|
47 |
//SYSTEM SET |
|
|
48 |
//1 Byte: System: |
|
|
49 |
//SYSTEM_GAMEBOY 0x04 (SYSTEM_TOTAL_CHANNELS 4) |
|
|
50 |
if DS.ReadByte <> $04 then |
|
|
51 |
raise EDMFException.Create('DMF file is not a Game Boy module!'); |
|
|
52 |
|
|
|
53 |
//VISUAL INFORMATION |
|
|
54 |
// 1 Byte: Song Name Chars Count (0-255) |
|
|
55 |
// N Bytes: Song Name Chars |
|
|
56 |
Result.Name := DS.ReadDMFString; |
|
|
57 |
writeln(result.name); |
|
|
58 |
|
|
|
59 |
// 1 Byte: Song Author Chars Count (0-255) |
|
|
60 |
// N Bytes: Song Author Chars |
|
|
61 |
Result.Artist := DS.ReadDMFString; |
|
|
62 |
writeln(result.artist); |
|
|
63 |
|
|
|
64 |
// 1 Byte: Highlight A in patterns |
|
|
65 |
// 1 Byte: Highlight B in patterns |
|
|
66 |
DS.Seek(2, soCurrent); |
|
|
67 |
|
|
|
68 |
//MODULE INFORMATION |
|
|
69 |
//1 Byte: Time Base |
|
|
70 |
DS.Seek(1, soCurrent); |
|
|
71 |
//1 Byte: Tick Time 1 |
|
|
72 |
//1 Byte: Tick Time 2 |
|
|
73 |
Result.TicksPerRow := DS.ReadByte; |
|
|
74 |
DS.Seek(1, soCurrent); |
|
|
75 |
//1 Byte: Frames Mode (0 = PAL, 1 = NTSC) |
|
|
76 |
//1 Byte: Using Custom HZ (If set to 1, NTSC or PAL is ignored) |
|
|
77 |
//1 Byte: Custom HZ value 1 |
|
|
78 |
//1 Byte: Custom HZ value 2 |
|
|
79 |
//1 Byte: Custom HZ value 3 |
|
|
80 |
DS.Seek(5, soCurrent); |
|
|
81 |
//4 Bytes: TOTAL_ROWS_PER_PATTERN |
|
|
82 |
Temp := DS.ReadQWord; |
|
|
83 |
Writeln(temp); |
|
|
84 |
if Temp <> 64 then |
|
|
85 |
raise EDMFException.Create('DMF file needs 64 rows per pattern!'); |
|
|
86 |
//1 Byte: TOTAL_ROWS_IN_PATTERN_MATRIX |
|
|
87 |
Temp := DS.ReadByte; |
|
|
88 |
for I := Low(Result.OrderMatrix) to High(Result.OrderMatrix) do |
|
|
89 |
SetLength(Result.OrderMatrix[I], Temp); |
|
|
90 |
|
|
|
91 |
//PATTERN MATRIX VALUES (A matrix of SYSTEM_TOTAL_CHANNELS x TOTAL_ROWS_IN_PATTERN_MATRIX) |
|
|
92 |
for I := Low(Result.OrderMatrix) to High(Result.OrderMatrix) do |
|
|
93 |
for J := Low(Result.OrderMatrix[I]) to High(Result.OrderMatrix[I]) do |
|
|
94 |
//1 Byte: Pattern Matrix Value: (Index from SYSTEM_TOTAL_CHANNELS loop, Index from TOTAL_ROWS_IN_PATTERN_MATRIX loop) |
|
|
95 |
Result.OrderMatrix[I, J] := (100*(I+1))+DS.ReadByte; |
|
|
96 |
//1 Byte: TOTAL_INSTRUMENTS |
|
|
97 |
TotalInstruments := DS.ReadByte; |
|
|
98 |
if TotalInstruments > 15 then |
|
|
99 |
raise EDMFException.Create('DMF file must have 15 instruments or less!'); |
|
|
100 |
|
|
|
101 |
// Read all instruments |
|
|
102 |
for I := 0 to TotalInstruments-1 do begin |
|
|
103 |
// 1 Byte: Instrument Name Chars Count (0-255) |
|
|
104 |
// N Bytes: Instrument Name Chars |
|
|
105 |
TempS := DS.ReadDMFString; |
|
|
106 |
Result.Instruments.Duty[I].Name := TempS; |
|
|
107 |
Result.Instruments.Wave[I].Name := TempS; |
|
|
108 |
Result.Instruments.Noise[I].Name := TempS; |
|
|
109 |
|
|
|
110 |
// 1 Byte: Instrument Mode (0 = STANDARD INS, 1 = FM INS) |
|
|
111 |
if DS.ReadByte <> 1 then |
|
|
112 |
raise EDMFException.Create('DMF file contains a non-standard instrument!'); |
|
|
113 |
|
|
|
114 |
//ARPEGGIO MACRO |
|
|
115 |
//1 Byte: ENVELOPE_SIZE (0 - 127) |
|
|
116 |
//Repeat this ENVELOPE_SIZE times |
|
|
117 |
//4 Bytes: ENVELOPE_VALUE (signed int, offset=12) |
|
|
118 |
//IF ENVELOPE_SIZE > 0 |
|
|
119 |
//1 Byte: LOOP_POSITION (-1 = NO LOOP) |
|
|
120 |
//1 Byte: ARPEGGIO MACRO MODE (0 = Normal, 1 = Fixed) |
|
|
121 |
Temp := DS.ReadByte; |
|
|
122 |
DS.Seek(4*Temp, soCurrent); |
|
|
123 |
if Temp > 0 then |
|
|
124 |
DS.Seek(1, soCurrent); |
|
|
125 |
DS.Seek(1, soCurrent); |
|
|
126 |
|
|
|
127 |
//DUTY/NOISE MACRO |
|
|
128 |
//1 Byte: ENVELOPE_SIZE (0 - 127) |
|
|
129 |
//Repeat this ENVELOPE_SIZE times |
|
|
130 |
//4 Bytes: ENVELOPE_VALUE |
|
|
131 |
//IF ENVELOPE_SIZE > 0 |
|
|
132 |
//1 Byte: LOOP_POSITION (-1 = NO LOOP) |
|
|
133 |
Temp := DS.ReadByte; |
|
|
134 |
DS.Seek(4*Temp, soCurrent); |
|
|
135 |
if Temp > 0 then |
|
|
136 |
DS.Seek(1, soCurrent); |
|
|
137 |
|
|
|
138 |
//WAVETABLE MACRO |
|
|
139 |
//1 Byte: ENVELOPE_SIZE (0 - 127) |
|
|
140 |
//Repeat this ENVELOPE_SIZE times |
|
|
141 |
//4 Bytes: ENVELOPE_VALUE |
|
|
142 |
//IF ENVELOPE_SIZE > 0 |
|
|
143 |
//1 Byte: LOOP_POSITION (-1 = NO LOOP) |
|
|
144 |
Temp := DS.ReadByte; |
|
|
145 |
DS.Seek(4*Temp, soCurrent); |
|
|
146 |
if Temp > 0 then |
|
|
147 |
DS.Seek(1, soCurrent); |
|
|
148 |
|
|
|
149 |
//1 Byte: Envelope Volume |
|
|
150 |
//1 Byte: Envelope Direction |
|
|
151 |
//1 Byte: Envelope Length |
|
|
152 |
//1 Byte: Sound Length |
|
|
153 |
DS.Seek(4, soCurrent); |
|
|
154 |
end; |
|
|
155 |
|
|
|
156 |
//WAVETABLES DATA |
|
|
157 |
//1 Byte: TOTAL_WAVETABLES |
|
|
158 |
//Repeat this TOTAL_WAVETABLES times |
|
|
159 |
// 4 Bytes: WAVETABLE_SIZE |
|
|
160 |
// Repeat this WAVETABLE_SIZE times |
|
|
161 |
// 4 Bytes: Wavetable Data |
|
|
162 |
TotalWavetables := DS.ReadByte; |
|
|
163 |
for I := 0 to TotalWavetables-1 do |
|
|
164 |
DS.Seek(DS.ReadQWord * 4, soCurrent); |
|
|
165 |
|
|
|
166 |
for I := Low(TOrderMatrix) to High(TOrderMatrix) do begin |
|
|
167 |
if DS.ReadByte <> 1 then |
|
|
168 |
raise EDMFException.Create('DMF file contains patterns with more than one effect column!'); |
|
|
169 |
|
|
|
170 |
for J := Low(Result.OrderMatrix[I]) to High(Result.OrderMatrix[I]) do begin |
|
|
171 |
Pat := Result.Patterns.GetOrCreateNew(((I+1)*100) + J); |
|
|
172 |
for Row := Low(TPattern) to High(TPattern) do begin |
|
|
173 |
with Pat^[Row] do begin |
|
|
174 |
Note := DS.ReadWord + (12*DS.ReadWord); |
|
|
175 |
EffectCode := DS.ReadWord; |
|
|
176 |
EffectParams.Value := DS.ReadWord; |
|
|
177 |
Instrument := DS.ReadWord; |
|
|
178 |
end; |
|
|
179 |
end; |
|
|
180 |
end; |
|
|
181 |
end; |
|
|
182 |
|
|
|
183 |
DS.Free; |
|
|
184 |
end; |
|
|
185 |
|
|
|
186 |
{ TDMFStream } |
|
|
187 |
|
|
|
188 |
function TDMFStream.ReadDMFString: String; |
|
|
189 |
Var |
|
|
190 |
TheSize : Byte; |
|
|
191 |
P : PByte ; |
|
|
192 |
begin |
|
|
193 |
ReadBuffer (TheSize,SizeOf(TheSize)); |
|
|
194 |
SetLength(Result,TheSize); |
|
|
195 |
// Illegal typecast if no AnsiStrings defined. |
|
|
196 |
if TheSize>0 then |
|
|
197 |
begin |
|
|
198 |
ReadBuffer (Pointer(Result)^,TheSize); |
|
|
199 |
P:=Pointer(Result)+TheSize; |
|
|
200 |
p^:=0; |
|
|
201 |
end; |
|
|
202 |
end; |
|
|
203 |
|
|
|
204 |
|
|
|
205 |
end. |
|
|
206 |
|