@@ -5,7 +5,8 @@ unit MODImport; |
| 5 |
interface |
5 |
interface |
| 6 |
|
6 |
|
| 7 |
uses |
7 |
uses |
| 8 |
Classes, SysUtils, Song, hugedatatypes, fgl, utils, constants; |
8 |
Classes, SysUtils, Song, hugedatatypes, fgl, math, utils, constants, |
|
|
9 |
instruments; |
| 9 |
|
10 |
|
| 10 |
type |
11 |
type |
| 11 |
TPeriodToCodeMap = specialize TFPGMap<Integer, Integer>; |
12 |
TPeriodToCodeMap = specialize TFPGMap<Integer, Integer>; |
@@ -65,6 +66,18 @@ type |
| 65 |
|
66 |
|
| 66 |
function LoadSongFromModStream(Stream: TStream): TSong; |
67 |
function LoadSongFromModStream(Stream: TStream): TSong; |
| 67 |
|
68 |
|
|
|
69 |
const |
|
|
70 |
// https://github.com/AntonioND/gbt-player/blob/master/rgbds_example/gbt_player_bank1.asm |
|
|
71 |
GBT_WAVEFORMS: array[0..7] of array[0..15] of Byte = |
|
|
72 |
(($A5,$D7,$C9,$E1,$BC,$9A,$76,$31,$0C,$BA,$DE,$60,$1B,$CA,$03,$93), |
|
|
73 |
($F0,$E1,$D2,$C3,$B4,$A5,$96,$87,$78,$69,$5A,$4B,$3C,$2D,$1E,$0F), |
|
|
74 |
($FD,$EC,$DB,$CA,$B9,$A8,$97,$86,$79,$68,$57,$46,$35,$24,$13,$02), |
|
|
75 |
($DE,$FE,$DC,$BA,$9A,$A9,$87,$77,$88,$87,$65,$56,$54,$32,$10,$12), |
|
|
76 |
($AB,$CD,$EF,$ED,$CB,$A0,$12,$3E,$DC,$BA,$BC,$DE,$FE,$DC,$32,$10), |
|
|
77 |
($FF,$EE,$DD,$CC,$BB,$AA,$99,$88,$77,$66,$55,$44,$33,$22,$11,$00), |
|
|
78 |
($FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00), |
|
|
79 |
($79,$BC,$DE,$EF,$FF,$EE,$DC,$B9,$75,$43,$21,$10,$00,$11,$23,$45)); |
|
|
80 |
|
| 68 |
var |
81 |
var |
| 69 |
PeriodsToCodeMap: TPeriodToCodeMap; |
82 |
PeriodsToCodeMap: TPeriodToCodeMap; |
| 70 |
|
83 |
|
@@ -88,24 +101,37 @@ end; |
| 88 |
|
101 |
|
| 89 |
function ConvertInstrument(Instr: Integer): Integer; |
102 |
function ConvertInstrument(Instr: Integer): Integer; |
| 90 |
begin |
103 |
begin |
| 91 |
if Instr = 0 then |
104 |
Result := EnsureRange(Instr, 0, 15); |
| 92 |
Result := 0 |
|
|
| 93 |
else |
|
|
| 94 |
Result := 1; |
|
|
| 95 |
end; |
105 |
end; |
| 96 |
|
106 |
|
| 97 |
procedure ConvertEffect(Code, Params: Integer; out OutCode: Integer; out OutParams: TEffectParams); |
107 |
procedure ConvertEffect(Code, Params: Integer; out OutCode: Integer; out OutParams: TEffectParams); |
|
|
108 |
var |
|
|
109 |
EP: TEffectParams absolute Params; |
|
|
110 |
label IdentityEffect; |
| 98 |
begin |
111 |
begin |
| 99 |
case Code of |
112 |
if Code in [$F, $B, $D] then begin |
| 100 |
$C: begin |
113 |
OutCode := Code; |
| 101 |
OutCode := $C; |
114 |
OutParams.Value := Params + 1; |
| 102 |
OutParams.Value := ; |
|
|
| 103 |
end; |
|
|
| 104 |
else begin |
|
|
| 105 |
OutCode := Code; |
|
|
| 106 |
OutParams := Params; |
|
|
| 107 |
end; |
|
|
| 108 |
end |
115 |
end |
|
|
116 |
else |
|
|
117 |
case Code of |
|
|
118 |
$C: begin |
|
|
119 |
OutCode := $C; |
|
|
120 |
OutParams.Value := Trunc((Params / $40)*$F); |
|
|
121 |
end; |
|
|
122 |
$E: begin |
|
|
123 |
if EP.Param1 = $C then begin |
|
|
124 |
OutCode := $E; |
|
|
125 |
OutParams.Value := EP.Param2; |
|
|
126 |
end |
|
|
127 |
else goto IdentityEffect; |
|
|
128 |
end; |
|
|
129 |
else begin |
|
|
130 |
IdentityEffect: |
|
|
131 |
OutCode := Code; |
|
|
132 |
OutParams.Value := Params; |
|
|
133 |
end; |
|
|
134 |
end |
| 109 |
end; |
135 |
end; |
| 110 |
|
136 |
|
| 111 |
function ConvertCell(MC: TMODRow): TCell; |
137 |
function ConvertCell(MC: TMODRow): TCell; |
@@ -115,15 +141,25 @@ begin |
| 115 |
ConvertEffect(MC.Effect.Code, MC.Effect.Params, Result.EffectCode, Result.EffectParams); |
141 |
ConvertEffect(MC.Effect.Code, MC.Effect.Params, Result.EffectCode, Result.EffectParams); |
| 116 |
end; |
142 |
end; |
| 117 |
|
143 |
|
| 118 |
procedure TranscribePattern(MP: TMODPattern; P1, P2, P3, P4: PPattern); |
144 |
procedure TranscribeColumn(MP: TMODPattern; Pat: PPattern; Column: Integer); |
| 119 |
var |
145 |
var |
| 120 |
I: Integer; |
146 |
I: Integer; |
|
|
147 |
LastPlayedNote: Integer; |
|
|
148 |
LastPlayedInstrument: Integer; |
| 121 |
begin |
149 |
begin |
|
|
150 |
LastPlayedNote := C_5; |
|
|
151 |
LastPlayedInstrument := 0; |
|
|
152 |
|
| 122 |
for I := Low(MP) to High(MP) do begin |
153 |
for I := Low(MP) to High(MP) do begin |
| 123 |
P1^[I] := ConvertCell(MP[I, 1]); |
154 |
Pat^[I] := ConvertCell(MP[I, Column]); |
| 124 |
P2^[I] := ConvertCell(MP[I, 2]); |
155 |
|
| 125 |
P3^[I] := ConvertCell(MP[I, 3]); |
156 |
if (Pat^[I].EffectCode = $C) and (Pat^[I].Note = NO_NOTE) then begin |
| 126 |
P4^[I] := ConvertCell(MP[I, 4]); |
157 |
Pat^[I].Note := LastPlayedNote; |
|
|
158 |
Pat^[I].Instrument := LastPlayedInstrument; |
|
|
159 |
end; |
|
|
160 |
|
|
|
161 |
if (Pat^[I].Note <> NO_NOTE) then LastPlayedNote := Pat^[I].Note; |
|
|
162 |
if (Pat^[I].Instrument <> 0) then LastPlayedInstrument := Pat^[I].Instrument; |
| 127 |
end; |
163 |
end; |
| 128 |
end; |
164 |
end; |
| 129 |
|
165 |
|
@@ -157,15 +193,39 @@ begin |
| 157 |
|
193 |
|
| 158 |
InitializeSong(Result); |
194 |
InitializeSong(Result); |
| 159 |
|
195 |
|
| 160 |
for I := Low(ModFile.Patterns) to High(ModFile.Patterns) do |
196 |
// Create the instruments. |
| 161 |
TranscribePattern(ModFile.Patterns[I], |
197 |
|
| 162 |
Result.Patterns.CreateNewPattern(I*10 + 0), |
198 |
// First four are just the default instrument with a different duty cycle. |
| 163 |
Result.Patterns.CreateNewPattern(I*10 + 1), |
199 |
Result.Instruments[1].Duty := 1; |
| 164 |
Result.Patterns.CreateNewPattern(I*10 + 2), |
200 |
Result.Instruments[2].Duty := 2; |
| 165 |
Result.Patterns.CreateNewPattern(I*10 + 3)); |
201 |
Result.Instruments[3].Duty := 3; |
|
|
202 |
Result.Instruments[4].Duty := 0; |
|
|
203 |
|
|
|
204 |
for I := 8 to 15 do |
|
|
205 |
with Result.Instruments[I] do begin |
|
|
206 |
Waveform := I - 8; |
|
|
207 |
Type_ := itWave; |
|
|
208 |
end; |
|
|
209 |
|
|
|
210 |
// Waveforms. |
|
|
211 |
for I := Low(GBT_WAVEFORMS) to High(GBT_WAVEFORMS) do |
|
|
212 |
for J := Low(GBT_WAVEFORMS[I]) to High(GBT_WAVEFORMS[I]) do begin |
|
|
213 |
Result.Waves[I, J*2] := hi(GBT_WAVEFORMS[I, J]); |
|
|
214 |
Result.Waves[I, (J*2) + 1] := lo(GBT_WAVEFORMS[I, J]); |
|
|
215 |
end; |
|
|
216 |
|
|
|
217 |
// Convert all patterns |
|
|
218 |
for I := Low(ModFile.Patterns) to High(ModFile.Patterns) do begin |
|
|
219 |
TranscribeColumn(ModFile.Patterns[I], Result.Patterns.CreateNewPattern(I*10 + 0), 1); |
|
|
220 |
TranscribeColumn(ModFile.Patterns[I], Result.Patterns.CreateNewPattern(I*10 + 1), 2); |
|
|
221 |
TranscribeColumn(ModFile.Patterns[I], Result.Patterns.CreateNewPattern(I*10 + 2), 3); |
|
|
222 |
TranscribeColumn(ModFile.Patterns[I], Result.Patterns.CreateNewPattern(I*10 + 3), 4); |
|
|
223 |
end; |
| 166 |
|
224 |
|
|
|
225 |
// Import the order table. Uses a weird numbering scheme because hUGE has |
|
|
226 |
// 4 separate patterns like AHX and unlike MOD. |
| 167 |
for I := Low(Result.OrderMatrix) to High(Result.OrderMatrix) do begin |
227 |
for I := Low(Result.OrderMatrix) to High(Result.OrderMatrix) do begin |
| 168 |
SetLength(Result.OrderMatrix[I], ModFile.SongLen); |
228 |
SetLength(Result.OrderMatrix[I], ModFile.SongLen+1); |
| 169 |
|
229 |
|
| 170 |
for J := 0 to ModFile.SongLen do |
230 |
for J := 0 to ModFile.SongLen do |
| 171 |
Result.OrderMatrix[I, J] := (ModFile.Positions[J]*10) + I; |
231 |
Result.OrderMatrix[I, J] := (ModFile.Positions[J]*10) + I; |