@@ -0,0 +1,253 @@ |
|
|
1 |
unit MODImport; |
|
|
2 |
|
|
|
3 |
{$mode objfpc}{$H+} |
|
|
4 |
|
|
|
5 |
interface |
|
|
6 |
|
|
|
7 |
uses |
|
|
8 |
Classes, SysUtils, Song, hugedatatypes, fgl, utils, constants; |
|
|
9 |
|
|
|
10 |
type |
|
|
11 |
TPeriodToCodeMap = specialize TFPGMap<Integer, Integer>; |
|
|
12 |
|
|
|
13 |
// TODO: Find some way to de-duplicate these from the instruments file |
|
|
14 |
// without a circular reference |
|
|
15 |
Bit = Boolean; |
|
|
16 |
TwoBits = 0..%11; |
|
|
17 |
ThreeBits = 0..%111; |
|
|
18 |
FourBits = 0..%1111; |
|
|
19 |
FiveBits = 0..%11111; |
|
|
20 |
SixBits = 0..%111111; |
|
|
21 |
SevenBits = 0..%1111111; |
|
|
22 |
EightBits = Byte; |
|
|
23 |
TwelveBits = 0..%111111111111; |
|
|
24 |
|
|
|
25 |
{ TMODSample } |
|
|
26 |
|
|
|
27 |
TMODSample = packed record |
|
|
28 |
Name: array[0..21] of char; //string[21]; |
|
|
29 |
SampleLength: Word; |
|
|
30 |
Finetune: Byte; |
|
|
31 |
Volume: Byte; |
|
|
32 |
RepeatPoint: Word; |
|
|
33 |
RepeatLength: Word; |
|
|
34 |
end; |
|
|
35 |
|
|
|
36 |
{ TMODRawRow } |
|
|
37 |
|
|
|
38 |
TMODRawRow = packed array[0..3] of Byte; |
|
|
39 |
|
|
|
40 |
{ TMODRow } |
|
|
41 |
|
|
|
42 |
TMODRow = bitpacked record |
|
|
43 |
Note: Integer; |
|
|
44 |
Instrument: Integer; |
|
|
45 |
Effect: bitpacked record |
|
|
46 |
case Boolean of |
|
|
47 |
True: (Code, Params: Byte); |
|
|
48 |
False: (Value: Word); |
|
|
49 |
end |
|
|
50 |
end; |
|
|
51 |
|
|
|
52 |
TMODPattern = array[0..63] of array[1..4] of TMODRow; |
|
|
53 |
|
|
|
54 |
{ TMODFile } |
|
|
55 |
|
|
|
56 |
TMODFile = packed record |
|
|
57 |
Name: array[0..19] of Char; //string[19]; |
|
|
58 |
Samples: array[1..31] of TMODSample; |
|
|
59 |
SongLen: Byte; |
|
|
60 |
NumPatternsMagic: Byte; |
|
|
61 |
Positions: array[0..127] of Byte; |
|
|
62 |
MKMagic: array[0..3] of char; //string[3]; |
|
|
63 |
Patterns: array of TMODPattern; |
|
|
64 |
end; |
|
|
65 |
|
|
|
66 |
function LoadSongFromModStream(Stream: TStream): TSong; |
|
|
67 |
|
|
|
68 |
var |
|
|
69 |
PeriodsToCodeMap: TPeriodToCodeMap; |
|
|
70 |
|
|
|
71 |
implementation |
|
|
72 |
|
|
|
73 |
function RawRowToRegularRow(RawRow: TMODRawRow): TMODRow; |
|
|
74 |
begin |
|
|
75 |
Result.Note := ((RawRow[0] and %1111) shl 8) or RawRow[1]; |
|
|
76 |
Result.Effect.Code := RawRow[2] and %1111; |
|
|
77 |
Result.Effect.Params := RawRow[3]; |
|
|
78 |
Result.Instrument := (RawRow[0] and %11110000) or ((RawRow[2] and %11110000) shr 4); |
|
|
79 |
end; |
|
|
80 |
|
|
|
81 |
function ConvertNote(Note: Integer): Integer; |
|
|
82 |
begin |
|
|
83 |
if Note = 0 then |
|
|
84 |
Result := NO_NOTE |
|
|
85 |
else |
|
|
86 |
Result := PeriodsToCodeMap.KeyData[Note]; |
|
|
87 |
end; |
|
|
88 |
|
|
|
89 |
function ConvertInstrument(Instr: Integer): Integer; |
|
|
90 |
begin |
|
|
91 |
if Instr = 0 then |
|
|
92 |
Result := 0 |
|
|
93 |
else |
|
|
94 |
Result := 1; |
|
|
95 |
end; |
|
|
96 |
|
|
|
97 |
procedure ConvertEffect(Code, Params: Integer; out OutCode: Integer; out OutParams: TEffectParams); |
|
|
98 |
begin |
|
|
99 |
case Code of |
|
|
100 |
$C: begin |
|
|
101 |
OutCode := $C; |
|
|
102 |
OutParams.Value := ; |
|
|
103 |
end; |
|
|
104 |
else begin |
|
|
105 |
OutCode := Code; |
|
|
106 |
OutParams := Params; |
|
|
107 |
end; |
|
|
108 |
end |
|
|
109 |
end; |
|
|
110 |
|
|
|
111 |
function ConvertCell(MC: TMODRow): TCell; |
|
|
112 |
begin |
|
|
113 |
Result.Note := ConvertNote(MC.Note); |
|
|
114 |
Result.Instrument := ConvertInstrument(MC.Instrument); |
|
|
115 |
ConvertEffect(MC.Effect.Code, MC.Effect.Params, Result.EffectCode, Result.EffectParams); |
|
|
116 |
end; |
|
|
117 |
|
|
|
118 |
procedure TranscribePattern(MP: TMODPattern; P1, P2, P3, P4: PPattern); |
|
|
119 |
var |
|
|
120 |
I: Integer; |
|
|
121 |
begin |
|
|
122 |
for I := Low(MP) to High(MP) do begin |
|
|
123 |
P1^[I] := ConvertCell(MP[I, 1]); |
|
|
124 |
P2^[I] := ConvertCell(MP[I, 2]); |
|
|
125 |
P3^[I] := ConvertCell(MP[I, 3]); |
|
|
126 |
P4^[I] := ConvertCell(MP[I, 4]); |
|
|
127 |
end; |
|
|
128 |
end; |
|
|
129 |
|
|
|
130 |
function LoadSongFromModStream(Stream: TStream): TSong; |
|
|
131 |
var |
|
|
132 |
ModFile: TMODFile; |
|
|
133 |
I, J, K: Integer; |
|
|
134 |
MaxOrder: Integer; |
|
|
135 |
RawRow: TMODRawRow; |
|
|
136 |
begin |
|
|
137 |
Stream.Read(ModFile, SizeOf(ModFile) - SizeOf(ModFile.Patterns)); |
|
|
138 |
for I := Low(ModFile.Samples) to High(ModFile.Samples) do |
|
|
139 |
with ModFile.Samples[I] do begin |
|
|
140 |
SampleLength := BEtoN(SampleLength); |
|
|
141 |
RepeatPoint := BEtoN(RepeatPoint); |
|
|
142 |
RepeatLength := BEtoN(RepeatLength); |
|
|
143 |
end; |
|
|
144 |
|
|
|
145 |
MaxOrder := 0; |
|
|
146 |
for I := Low(ModFile.Positions) to High(ModFile.Positions) do |
|
|
147 |
if ModFile.Positions[I] > MaxOrder then |
|
|
148 |
MaxOrder := ModFile.Positions[I]; |
|
|
149 |
|
|
|
150 |
SetLength(ModFile.Patterns, MaxOrder+1); |
|
|
151 |
for I := 0 to MaxOrder do |
|
|
152 |
for J := Low(TMODPattern) to High(TMODPattern) do |
|
|
153 |
for K := 1 to 4 do begin |
|
|
154 |
Stream.Read(RawRow, SizeOf(TMODRawRow)); |
|
|
155 |
ModFile.Patterns[I, J, K] := RawRowToRegularRow(RawRow); |
|
|
156 |
end; |
|
|
157 |
|
|
|
158 |
InitializeSong(Result); |
|
|
159 |
|
|
|
160 |
for I := Low(ModFile.Patterns) to High(ModFile.Patterns) do |
|
|
161 |
TranscribePattern(ModFile.Patterns[I], |
|
|
162 |
Result.Patterns.CreateNewPattern(I*10 + 0), |
|
|
163 |
Result.Patterns.CreateNewPattern(I*10 + 1), |
|
|
164 |
Result.Patterns.CreateNewPattern(I*10 + 2), |
|
|
165 |
Result.Patterns.CreateNewPattern(I*10 + 3)); |
|
|
166 |
|
|
|
167 |
for I := Low(Result.OrderMatrix) to High(Result.OrderMatrix) do begin |
|
|
168 |
SetLength(Result.OrderMatrix[I], ModFile.SongLen); |
|
|
169 |
|
|
|
170 |
for J := 0 to ModFile.SongLen do |
|
|
171 |
Result.OrderMatrix[I, J] := (ModFile.Positions[J]*10) + I; |
|
|
172 |
end; |
|
|
173 |
|
|
|
174 |
Result.Name := ModFile.Name; |
|
|
175 |
end; |
|
|
176 |
|
|
|
177 |
begin |
|
|
178 |
PeriodsToCodeMap := TPeriodToCodeMap.Create; |
|
|
179 |
|
|
|
180 |
PeriodsToCodeMap.add(1712,0); |
|
|
181 |
PeriodsToCodeMap.add(1616,1); |
|
|
182 |
PeriodsToCodeMap.add(1524,2); |
|
|
183 |
PeriodsToCodeMap.add(1440,3); |
|
|
184 |
PeriodsToCodeMap.add(1356,4); |
|
|
185 |
PeriodsToCodeMap.add(1280,5); |
|
|
186 |
PeriodsToCodeMap.add(1208,6); |
|
|
187 |
PeriodsToCodeMap.add(1140,7); |
|
|
188 |
PeriodsToCodeMap.add(1076,8); |
|
|
189 |
PeriodsToCodeMap.add(1016,9); |
|
|
190 |
PeriodsToCodeMap.add(960,10); |
|
|
191 |
PeriodsToCodeMap.add(907,11); |
|
|
192 |
PeriodsToCodeMap.add(856,12); |
|
|
193 |
PeriodsToCodeMap.add(808,13); |
|
|
194 |
PeriodsToCodeMap.add(762,14); |
|
|
195 |
PeriodsToCodeMap.add(720,15); |
|
|
196 |
PeriodsToCodeMap.add(678,16); |
|
|
197 |
PeriodsToCodeMap.add(640,17); |
|
|
198 |
PeriodsToCodeMap.add(604,18); |
|
|
199 |
PeriodsToCodeMap.add(570,19); |
|
|
200 |
PeriodsToCodeMap.add(538,20); |
|
|
201 |
PeriodsToCodeMap.add(508,21); |
|
|
202 |
PeriodsToCodeMap.add(480,22); |
|
|
203 |
PeriodsToCodeMap.add(453,23); |
|
|
204 |
PeriodsToCodeMap.add(428,24); |
|
|
205 |
PeriodsToCodeMap.add(404,25); |
|
|
206 |
PeriodsToCodeMap.add(381,26); |
|
|
207 |
PeriodsToCodeMap.add(360,27); |
|
|
208 |
PeriodsToCodeMap.add(339,28); |
|
|
209 |
PeriodsToCodeMap.add(320,29); |
|
|
210 |
PeriodsToCodeMap.add(302,30); |
|
|
211 |
PeriodsToCodeMap.add(285,31); |
|
|
212 |
PeriodsToCodeMap.add(269,32); |
|
|
213 |
PeriodsToCodeMap.add(254,33); |
|
|
214 |
PeriodsToCodeMap.add(240,34); |
|
|
215 |
PeriodsToCodeMap.add(226,35); |
|
|
216 |
PeriodsToCodeMap.add(214,36); |
|
|
217 |
PeriodsToCodeMap.add(202,37); |
|
|
218 |
PeriodsToCodeMap.add(190,38); |
|
|
219 |
PeriodsToCodeMap.add(180,39); |
|
|
220 |
PeriodsToCodeMap.add(170,40); |
|
|
221 |
PeriodsToCodeMap.add(160,41); |
|
|
222 |
PeriodsToCodeMap.add(151,42); |
|
|
223 |
PeriodsToCodeMap.add(143,43); |
|
|
224 |
PeriodsToCodeMap.add(135,44); |
|
|
225 |
PeriodsToCodeMap.add(127,45); |
|
|
226 |
PeriodsToCodeMap.add(120,46); |
|
|
227 |
PeriodsToCodeMap.add(113,47); |
|
|
228 |
PeriodsToCodeMap.add(107,48); |
|
|
229 |
PeriodsToCodeMap.add(101,49); |
|
|
230 |
PeriodsToCodeMap.add(95, 50); |
|
|
231 |
PeriodsToCodeMap.add(90, 51); |
|
|
232 |
PeriodsToCodeMap.add(85, 52); |
|
|
233 |
PeriodsToCodeMap.add(80, 53); |
|
|
234 |
PeriodsToCodeMap.add(75, 54); |
|
|
235 |
PeriodsToCodeMap.add(71, 55); |
|
|
236 |
PeriodsToCodeMap.add(67, 56); |
|
|
237 |
PeriodsToCodeMap.add(63, 57); |
|
|
238 |
PeriodsToCodeMap.add(60, 58); |
|
|
239 |
PeriodsToCodeMap.add(56, 59); |
|
|
240 |
PeriodsToCodeMap.add(53, 60); |
|
|
241 |
PeriodsToCodeMap.add(50, 61); |
|
|
242 |
PeriodsToCodeMap.add(47, 62); |
|
|
243 |
PeriodsToCodeMap.add(45, 63); |
|
|
244 |
PeriodsToCodeMap.add(42, 64); |
|
|
245 |
PeriodsToCodeMap.add(40, 65); |
|
|
246 |
PeriodsToCodeMap.add(37, 66); |
|
|
247 |
PeriodsToCodeMap.add(35, 67); |
|
|
248 |
PeriodsToCodeMap.add(33, 68); |
|
|
249 |
PeriodsToCodeMap.add(31, 69); |
|
|
250 |
PeriodsToCodeMap.add(30, 70); |
|
|
251 |
PeriodsToCodeMap.add(28, 71); |
|
|
252 |
end. |
|
|
253 |
|