@@ -7,7 +7,7 @@ interface |
| 7 |
uses |
7 |
uses |
| 8 |
Classes, SysUtils, Math, Instruments, Song, Utils, |
8 |
Classes, SysUtils, Math, Instruments, Song, Utils, |
| 9 |
HugeDatatypes, Constants, Dialogs, strutils, FileUtil, LazFileUtils, |
9 |
HugeDatatypes, Constants, Dialogs, strutils, FileUtil, LazFileUtils, |
| 10 |
lclintf, process, rgb2sdas; |
10 |
lclintf, process; |
| 11 |
|
11 |
|
| 12 |
type |
12 |
type |
| 13 |
TExportMode = (emNormal, emPreview, emGBS); |
13 |
TExportMode = (emNormal, emPreview, emGBS); |
@@ -192,186 +192,220 @@ begin |
| 192 |
OutSL.Free; |
192 |
OutSL.Free; |
| 193 |
end; |
193 |
end; |
| 194 |
|
194 |
|
| 195 |
function RenderOrderTable(OrderMatrix: TOrderMatrix): string; |
195 |
procedure RenderSongToHTTs(Song: TSong; DescriptorName: String; Filename: string); |
|
|
196 |
function RenderOrderTable(OrderMatrix: TOrderMatrix): string; |
|
|
197 |
|
|
|
198 |
function ArrayHelper(Ints: array of integer): string; |
|
|
199 |
var |
|
|
200 |
I: integer; |
|
|
201 |
SL: TStringList; |
|
|
202 |
begin |
|
|
203 |
SL := TStringList.Create; |
|
|
204 |
SL.StrictDelimiter := True; |
|
|
205 |
SL.Delimiter := ','; |
|
|
206 |
for I := Low(Ints) to High(Ints)-1 do // HACK: account for the off-by-one error |
|
|
207 |
SL.Add('P' + IntToStr(Ints[I])); |
|
|
208 |
Result := SL.DelimitedText; |
|
|
209 |
SL.Free; |
|
|
210 |
end; |
| 196 |
|
211 |
|
| 197 |
function ArrayHelper(Ints: array of integer): string; |
|
|
| 198 |
var |
212 |
var |
| 199 |
I: integer; |
213 |
Res: TStringList; |
| 200 |
SL: TStringList; |
214 |
OrderCnt: integer; |
| 201 |
begin |
215 |
begin |
| 202 |
SL := TStringList.Create; |
216 |
Res := TStringList.Create; |
| 203 |
SL.StrictDelimiter := True; |
217 |
OrderCnt := MaxIntValue([High(OrderMatrix[0]), High(OrderMatrix[1]), |
| 204 |
SL.Delimiter := ','; |
218 |
High(OrderMatrix[2]), High(OrderMatrix[3])]); |
| 205 |
for I := Low(Ints) to High(Ints)-1 do // HACK: account for the off-by-one error |
219 |
|
| 206 |
SL.Add('P' + IntToStr(Ints[I])); |
220 |
Res.Add('order_cnt: db ' + IntToStr(OrderCnt * 2)); |
| 207 |
Result := SL.DelimitedText; |
221 |
Res.Add('order1: dw ' + ArrayHelper(OrderMatrix[0])); |
| 208 |
SL.Free; |
222 |
Res.Add('order2: dw ' + ArrayHelper(OrderMatrix[1])); |
|
|
223 |
Res.Add('order3: dw ' + ArrayHelper(OrderMatrix[2])); |
|
|
224 |
Res.Add('order4: dw ' + ArrayHelper(OrderMatrix[3])); |
|
|
225 |
|
|
|
226 |
Result := Res.Text; |
|
|
227 |
Res.Free; |
| 209 |
end; |
228 |
end; |
| 210 |
|
229 |
|
| 211 |
var |
230 |
function RenderInstruments(Instruments: TInstrumentBank): string; |
| 212 |
Res: TStringList; |
231 |
var |
| 213 |
OrderCnt: integer; |
232 |
SL, ResultSL: TStringList; |
| 214 |
begin |
233 |
AsmInstrument: TAsmInstrument; |
| 215 |
Res := TStringList.Create; |
234 |
I, J: integer; |
| 216 |
OrderCnt := MaxIntValue([High(OrderMatrix[0]), High(OrderMatrix[1]), |
235 |
TypePrefix: string; |
| 217 |
High(OrderMatrix[2]), High(OrderMatrix[3])]); |
236 |
HighMask: byte; |
|
|
237 |
begin |
|
|
238 |
ResultSL := TStringList.Create; |
| 218 |
|
239 |
|
| 219 |
Res.Add('order_cnt: db ' + IntToStr(OrderCnt * 2)); |
240 |
for I := Low(Instruments) to High(Instruments) do |
| 220 |
Res.Add('order1: dw ' + ArrayHelper(OrderMatrix[0])); |
241 |
begin |
| 221 |
Res.Add('order2: dw ' + ArrayHelper(OrderMatrix[1])); |
242 |
AsmInstrument := InstrumentToBytes(Instruments[I]); |
| 222 |
Res.Add('order3: dw ' + ArrayHelper(OrderMatrix[2])); |
243 |
SL := TStringList.Create; |
| 223 |
Res.Add('order4: dw ' + ArrayHelper(OrderMatrix[3])); |
244 |
SL.StrictDelimiter := True; |
|
|
245 |
SL.Delimiter := ','; |
| 224 |
|
246 |
|
| 225 |
Result := Res.Text; |
247 |
if Instruments[I].Type_ = itNoise then |
| 226 |
Res.Free; |
248 |
begin |
| 227 |
end; |
249 |
SL.Add(IntToStr(AsmInstrument[1])); // envelope |
|
|
250 |
|
|
|
251 |
HighMask := AsmInstrument[0]; |
|
|
252 |
if Instruments[I].LengthEnabled then |
|
|
253 |
HighMask := HighMask or %01000000; |
|
|
254 |
if Instruments[I].CounterStep = swSeven then |
|
|
255 |
HighMask := HighMask or %10000000; |
|
|
256 |
SL.Add(IntToStr(HighMask)); |
|
|
257 |
|
|
|
258 |
for J := Low(TNoiseMacro) to High(TNoiseMacro) do |
|
|
259 |
SL.Add(IntToStr(Instruments[I].NoiseMacro[J])); |
|
|
260 |
end |
|
|
261 |
else |
|
|
262 |
for J := Low(AsmInstrument) to High(AsmInstrument) do |
|
|
263 |
SL.Add(IntToStr(AsmInstrument[J])); |
|
|
264 |
|
|
|
265 |
WriteStr(TypePrefix, Instruments[I].Type_); |
|
|
266 |
ResultSL.Add(Format('%s%s: db %s', [TypePrefix, 'inst' + IntToStr(I), |
|
|
267 |
SL.DelimitedText])); |
|
|
268 |
SL.Free; |
|
|
269 |
end; |
| 228 |
|
270 |
|
| 229 |
function RenderInstruments(Instruments: TInstrumentBank): string; |
271 |
Result := ResultSL.Text; |
| 230 |
var |
272 |
ResultSL.Free; |
| 231 |
SL, ResultSL: TStringList; |
273 |
end; |
| 232 |
AsmInstrument: TAsmInstrument; |
|
|
| 233 |
I, J: integer; |
|
|
| 234 |
TypePrefix: string; |
|
|
| 235 |
HighMask: byte; |
|
|
| 236 |
begin |
|
|
| 237 |
ResultSL := TStringList.Create; |
|
|
| 238 |
|
274 |
|
| 239 |
for I := Low(Instruments) to High(Instruments) do |
275 |
function RenderCell(Cell: TCell): string; |
|
|
276 |
var |
|
|
277 |
SL: TStringList; |
| 240 |
begin |
278 |
begin |
| 241 |
AsmInstrument := InstrumentToBytes(Instruments[I]); |
|
|
| 242 |
SL := TStringList.Create; |
279 |
SL := TStringList.Create; |
| 243 |
SL.StrictDelimiter := True; |
|
|
| 244 |
SL.Delimiter := ','; |
280 |
SL.Delimiter := ','; |
|
|
281 |
SL.StrictDelimiter := True; |
| 245 |
|
282 |
|
| 246 |
if Instruments[I].Type_ = itNoise then |
283 |
if Cell.Note = NO_NOTE then |
| 247 |
begin |
284 |
SL.Add('___') |
| 248 |
SL.Add(IntToStr(AsmInstrument[1])); // envelope |
285 |
else |
| 249 |
|
286 |
SL.Add(NoteToDriverMap.KeyData[Cell.Note]); |
| 250 |
HighMask := AsmInstrument[0]; |
|
|
| 251 |
if Instruments[I].LengthEnabled then |
|
|
| 252 |
HighMask := HighMask or %01000000; |
|
|
| 253 |
if Instruments[I].CounterStep = swSeven then |
|
|
| 254 |
HighMask := HighMask or %10000000; |
|
|
| 255 |
SL.Add(IntToStr(HighMask)); |
|
|
| 256 |
|
287 |
|
| 257 |
for J := Low(TNoiseMacro) to High(TNoiseMacro) do |
288 |
if InRange(Cell.Instrument, 0, 15) then |
| 258 |
SL.Add(IntToStr(Instruments[I].NoiseMacro[J])); |
289 |
SL.Add(IntToStr(Cell.Instrument)) |
| 259 |
end |
|
|
| 260 |
else |
290 |
else |
| 261 |
for J := Low(AsmInstrument) to High(AsmInstrument) do |
291 |
SL.Add('0'); |
| 262 |
SL.Add(IntToStr(AsmInstrument[J])); |
|
|
| 263 |
|
292 |
|
| 264 |
WriteStr(TypePrefix, Instruments[I].Type_); |
293 |
SL.Add('$' + EffectCodeToStr(Cell.EffectCode, Cell.EffectParams)); |
| 265 |
ResultSL.Add(Format('%s%s: db %s', [TypePrefix, 'inst' + IntToStr(I), |
294 |
|
| 266 |
SL.DelimitedText])); |
295 |
// RGBDS thinks you're defining a new macro if you don't have a space first. |
|
|
296 |
Result := ' dn ' + SL.DelimitedText; |
| 267 |
SL.Free; |
297 |
SL.Free; |
| 268 |
end; |
298 |
end; |
| 269 |
|
299 |
|
| 270 |
Result := ResultSL.Text; |
300 |
function RenderPattern(Name: string; Pattern: TPattern): string; |
| 271 |
ResultSL.Free; |
301 |
var |
| 272 |
end; |
302 |
SL: TStringList; |
| 273 |
|
303 |
I: integer; |
| 274 |
function RenderCell(Cell: TCell): string; |
304 |
begin |
| 275 |
var |
305 |
SL := TStringList.Create; |
| 276 |
SL: TStringList; |
306 |
SL.Add(Name + ':'); |
| 277 |
begin |
|
|
| 278 |
SL := TStringList.Create; |
|
|
| 279 |
SL.Delimiter := ','; |
|
|
| 280 |
SL.StrictDelimiter := True; |
|
|
| 281 |
|
|
|
| 282 |
if Cell.Note = NO_NOTE then |
|
|
| 283 |
SL.Add('___') |
|
|
| 284 |
else |
|
|
| 285 |
SL.Add(NoteToDriverMap.KeyData[Cell.Note]); |
|
|
| 286 |
|
|
|
| 287 |
if InRange(Cell.Instrument, 0, 15) then |
|
|
| 288 |
SL.Add(IntToStr(Cell.Instrument)) |
|
|
| 289 |
else |
|
|
| 290 |
SL.Add('0'); |
|
|
| 291 |
|
307 |
|
| 292 |
SL.Add('$' + EffectCodeToStr(Cell.EffectCode, Cell.EffectParams)); |
308 |
for I := Low(TPattern) to High(TPattern) do |
|
|
309 |
SL.Add(RenderCell(Pattern[I])); |
| 293 |
|
310 |
|
| 294 |
// RGBDS thinks you're defining a new macro if you don't have a space first. |
311 |
Result := SL.Text; |
| 295 |
Result := ' dn ' + SL.DelimitedText; |
312 |
SL.Free; |
| 296 |
SL.Free; |
313 |
end; |
| 297 |
end; |
|
|
| 298 |
|
314 |
|
| 299 |
function RenderPattern(Name: string; Pattern: TPattern): string; |
315 |
function RenderWaveforms(Waves: TWaveBank): string; |
| 300 |
var |
316 |
var |
| 301 |
SL: TStringList; |
317 |
SL, ResultSL: TStringList; |
| 302 |
I: integer; |
318 |
I, J: integer; |
| 303 |
begin |
319 |
begin |
| 304 |
SL := TStringList.Create; |
320 |
ResultSL := TStringList.Create; |
| 305 |
SL.Add(Name + ':'); |
|
|
| 306 |
|
321 |
|
| 307 |
for I := Low(TPattern) to High(TPattern) do |
322 |
for I := Low(Waves) to High(Waves) do |
| 308 |
SL.Add(RenderCell(Pattern[I])); |
323 |
begin |
|
|
324 |
SL := TStringList.Create; |
|
|
325 |
SL.StrictDelimiter := True; |
|
|
326 |
SL.Delimiter := ','; |
| 309 |
|
327 |
|
| 310 |
Result := SL.Text; |
328 |
J := Low(Waves[I]); |
| 311 |
SL.Free; |
329 |
while J < High(Waves[I]) do |
| 312 |
end; |
330 |
begin |
|
|
331 |
SL.Add(IntToStr((Waves[I, J] shl 4) or Waves[I, J + 1])); |
|
|
332 |
Inc(J, 2); |
|
|
333 |
end; |
|
|
334 |
ResultSL.Add(Format('wave%d: db %s', [I, SL.DelimitedText])); |
|
|
335 |
SL.Free; |
|
|
336 |
end; |
| 313 |
|
337 |
|
| 314 |
function RenderWaveforms(Waves: TWaveBank): string; |
338 |
Result := ResultSL.Text; |
| 315 |
var |
339 |
ResultSL.Free; |
| 316 |
SL, ResultSL: TStringList; |
340 |
end; |
| 317 |
I, J: integer; |
|
|
| 318 |
begin |
|
|
| 319 |
ResultSL := TStringList.Create; |
|
|
| 320 |
|
341 |
|
| 321 |
for I := Low(Waves) to High(Waves) do |
342 |
function RenderConstants(Song: TSong; Mode: TExportMode): string; |
|
|
343 |
var |
|
|
344 |
SL: TStringList; |
| 322 |
begin |
345 |
begin |
| 323 |
SL := TStringList.Create; |
346 |
SL := TStringList.Create; |
| 324 |
SL.StrictDelimiter := True; |
347 |
SL.Add('STANDALONE_MODE EQU 1'); |
| 325 |
SL.Delimiter := ','; |
|
|
| 326 |
|
348 |
|
| 327 |
J := Low(Waves[I]); |
349 |
if Mode = emPreview then |
| 328 |
while J < High(Waves[I]) do |
350 |
SL.Add('PREVIEW_MODE EQU 1') |
|
|
351 |
else if Mode = emGBS then |
| 329 |
begin |
352 |
begin |
| 330 |
SL.Add(IntToStr((Waves[I, J] shl 4) or Waves[I, J + 1])); |
353 |
SL.Add('GBS_MODE EQU 1'); |
| 331 |
Inc(J, 2); |
354 |
SL.Add('GBS_TITLE EQUS "\"' + PadRight(Song.Name, 32) + '\""'); |
|
|
355 |
SL.Add('GBS_AUTHOR EQUS "\"' + PadRight(Song.Artist, 32) + '\""'); |
|
|
356 |
SL.Add('GBS_COPYRIGHT EQUS "\"' + PadRight(IntToStr(CurrentYear), 32) + '\""'); |
| 332 |
end; |
357 |
end; |
| 333 |
ResultSL.Add(Format('wave%d: db %s', [I, SL.DelimitedText])); |
358 |
SL.Add('TICKS EQU ' + IntToStr(Song.TicksPerRow)); |
|
|
359 |
|
|
|
360 |
Result := SL.Text; |
| 334 |
SL.Free; |
361 |
SL.Free; |
| 335 |
end; |
362 |
end; |
| 336 |
|
363 |
|
| 337 |
Result := ResultSL.Text; |
364 |
procedure WriteRoutinesToFile(Routines: TRoutineBank); |
| 338 |
ResultSL.Free; |
365 |
var |
| 339 |
end; |
366 |
I: integer; |
| 340 |
|
367 |
F: Text; |
| 341 |
function RenderConstants(Song: TSong; Mode: TExportMode): string; |
|
|
| 342 |
var |
|
|
| 343 |
SL: TStringList; |
|
|
| 344 |
begin |
|
|
| 345 |
SL := TStringList.Create; |
|
|
| 346 |
SL.Add('STANDALONE_MODE EQU 1'); |
|
|
| 347 |
|
|
|
| 348 |
if Mode = emPreview then |
|
|
| 349 |
SL.Add('PREVIEW_MODE EQU 1') |
|
|
| 350 |
else if Mode = emGBS then |
|
|
| 351 |
begin |
368 |
begin |
| 352 |
SL.Add('GBS_MODE EQU 1'); |
369 |
for I := Low(TRoutineBank) to High(TRoutineBank) do |
| 353 |
SL.Add('GBS_TITLE EQUS "\"' + PadRight(Song.Name, 32) + '\""'); |
370 |
begin |
| 354 |
SL.Add('GBS_AUTHOR EQUS "\"' + PadRight(Song.Artist, 32) + '\""'); |
371 |
AssignFile(F, './hUGEDriver/routine' + IntToStr(I) + '.htt'); |
| 355 |
SL.Add('GBS_COPYRIGHT EQUS "\"' + PadRight(IntToStr(CurrentYear), 32) + '\""'); |
372 |
Rewrite(F); |
|
|
373 |
Write(F, Routines[I]); |
|
|
374 |
CloseFile(F); |
|
|
375 |
end; |
| 356 |
end; |
376 |
end; |
| 357 |
SL.Add('TICKS EQU ' + IntToStr(Song.TicksPerRow)); |
|
|
| 358 |
|
|
|
| 359 |
Result := SL.Text; |
|
|
| 360 |
SL.Free; |
|
|
| 361 |
end; |
|
|
| 362 |
|
377 |
|
| 363 |
procedure WriteRoutinesToFile(Routines: TRoutineBank); |
378 |
procedure WriteHTT(F: string; S: string); |
| 364 |
var |
|
|
| 365 |
I: integer; |
|
|
| 366 |
F: Text; |
|
|
| 367 |
begin |
|
|
| 368 |
for I := Low(TRoutineBank) to High(TRoutineBank) do |
|
|
| 369 |
begin |
379 |
begin |
| 370 |
AssignFile(F, './hUGEDriver/routine' + IntToStr(I) + '.htt'); |
380 |
AssignFile(OutFile, F); |
| 371 |
Rewrite(F); |
381 |
Rewrite(OutFile); |
| 372 |
Write(F, Routines[I]); |
382 |
Write(OutFile, S); |
| 373 |
CloseFile(F); |
383 |
CloseFile(OutFile); |
| 374 |
end; |
384 |
end; |
|
|
385 |
begin |
|
|
386 |
FilePath := Filename; |
|
|
387 |
Filename := ExtractFileNameWithoutExt(ExtractFileNameOnly(Filename)); |
|
|
388 |
|
|
|
389 |
WriteHTT('./hUGEDriver/'+DescriptorName+'_constants.htt', RenderConstants(Song, Mode)); |
|
|
390 |
WriteHTT('./hUGEDriver/'+DescriptorName+'_wave.htt', RenderWaveforms(Song.Waves)); |
|
|
391 |
WriteHTT('./hUGEDriver/'+DescriptorName+'_order.htt', RenderOrderTable(Song.OrderMatrix)); |
|
|
392 |
WriteHTT('./hUGEDriver/'+DescriptorName+'_duty_instrument.htt', RenderInstruments(Song.Instruments.Duty)); |
|
|
393 |
WriteHTT('./hUGEDriver/'+DescriptorName+'_wave_instrument.htt', RenderInstruments(Song.Instruments.Wave)); |
|
|
394 |
WriteHTT('./hUGEDriver/'+DescriptorName+'_noise_instrument.htt', |
|
|
395 |
RenderInstruments(Song.Instruments.Noise)); |
|
|
396 |
WriteRoutinesToFile(Song.Routines); |
|
|
397 |
|
|
|
398 |
AssignFile(OutFile, './hUGEDriver/pattern.htt'); |
|
|
399 |
Rewrite(OutFile); |
|
|
400 |
|
|
|
401 |
// TODO: Are keys and data defined to be aligned? Seems like they are but |
|
|
402 |
// should probably find out if that's just an implementation detail... |
|
|
403 |
for I := 0 to Song.Patterns.Count - 1 do |
|
|
404 |
if PatternIsUsed(Song.Patterns.Keys[I], Song) then |
|
|
405 |
Write(OutFile, RenderPattern('P' + IntToStr(Song.Patterns.Keys[I]), |
|
|
406 |
Song.Patterns.Data[I]^)); |
|
|
407 |
|
|
|
408 |
CloseFile(OutFile); |
| 375 |
end; |
409 |
end; |
| 376 |
|
410 |
|
| 377 |
procedure RectifyGBSFile(GBSFile: string); |
411 |
procedure RectifyGBSFile(GBSFile: string); |
@@ -410,14 +444,6 @@ var |
| 410 |
FilePath: string; |
444 |
FilePath: string; |
| 411 |
RenameSucceeded: Boolean; |
445 |
RenameSucceeded: Boolean; |
| 412 |
|
446 |
|
| 413 |
procedure WriteHTT(F: string; S: string); |
|
|
| 414 |
begin |
|
|
| 415 |
AssignFile(OutFile, F); |
|
|
| 416 |
Rewrite(OutFile); |
|
|
| 417 |
Write(OutFile, S); |
|
|
| 418 |
CloseFile(OutFile); |
|
|
| 419 |
end; |
|
|
| 420 |
|
|
|
| 421 |
function Assemble(OutFile: string; InFile: string; Defines: array of string): integer; |
447 |
function Assemble(OutFile: string; InFile: string; Defines: array of string): integer; |
| 422 |
var |
448 |
var |
| 423 |
Define: string; |
449 |
Define: string; |
@@ -469,29 +495,7 @@ label |
| 469 |
begin |
495 |
begin |
| 470 |
Song := OptimizeSong(Song); |
496 |
Song := OptimizeSong(Song); |
| 471 |
|
497 |
|
| 472 |
FilePath := Filename; |
498 |
RenderSongToHTTs(Song, DescriptorName, Filename); |
| 473 |
Filename := ExtractFileNameWithoutExt(ExtractFileNameOnly(Filename)); |
|
|
| 474 |
|
|
|
| 475 |
WriteHTT('./hUGEDriver/constants.htt', RenderConstants(Song, Mode)); |
|
|
| 476 |
WriteHTT('./hUGEDriver/wave.htt', RenderWaveforms(Song.Waves)); |
|
|
| 477 |
WriteHTT('./hUGEDriver/order.htt', RenderOrderTable(Song.OrderMatrix)); |
|
|
| 478 |
WriteHTT('./hUGEDriver/duty_instrument.htt', RenderInstruments(Song.Instruments.Duty)); |
|
|
| 479 |
WriteHTT('./hUGEDriver/wave_instrument.htt', RenderInstruments(Song.Instruments.Wave)); |
|
|
| 480 |
WriteHTT('./hUGEDriver/noise_instrument.htt', |
|
|
| 481 |
RenderInstruments(Song.Instruments.Noise)); |
|
|
| 482 |
WriteRoutinesToFile(Song.Routines); |
|
|
| 483 |
|
|
|
| 484 |
AssignFile(OutFile, './hUGEDriver/pattern.htt'); |
|
|
| 485 |
Rewrite(OutFile); |
|
|
| 486 |
|
|
|
| 487 |
// TODO: Are keys and data defined to be aligned? Seems like they are but |
|
|
| 488 |
// should probably find out if that's just an implementation detail... |
|
|
| 489 |
for I := 0 to Song.Patterns.Count - 1 do |
|
|
| 490 |
if PatternIsUsed(Song.Patterns.Keys[I], Song) then |
|
|
| 491 |
Write(OutFile, RenderPattern('P' + IntToStr(Song.Patterns.Keys[I]), |
|
|
| 492 |
Song.Patterns.Data[I]^)); |
|
|
| 493 |
|
|
|
| 494 |
CloseFile(OutFile); |
|
|
| 495 |
|
499 |
|
| 496 |
// Build the file |
500 |
// Build the file |
| 497 |
Chdir('hUGEDriver'); |
501 |
Chdir('hUGEDriver'); |