Commit ed715f8

Nick Faro committed on
WIP tbm import
commit ed715f8e7435185442be13f77e02ed2d98d162b9 parent c5c2c54
4 changed files +316−10
ModifiedhUGETracker.lpi +6−1
@@ -208,7 +208,7 @@
208 <PackageName Value="LCL"/> 208 <PackageName Value="LCL"/>
209 </Item5> 209 </Item5>
210 </RequiredPackages> 210 </RequiredPackages>
211 <Units Count="25"> 211 <Units Count="26">
212 <Unit0> 212 <Unit0>
213 <Filename Value="hUGETracker.lpr"/> 213 <Filename Value="hUGETracker.lpr"/>
214 <IsPartOfProject Value="True"/> 214 <IsPartOfProject Value="True"/>
@@ -340,6 +340,11 @@
340 <IsPartOfProject Value="True"/> 340 <IsPartOfProject Value="True"/>
341 <UnitName Value="VGM"/> 341 <UnitName Value="VGM"/>
342 </Unit24> 342 </Unit24>
343 <Unit25>
344 <Filename Value="tbmimport.pas"/>
345 <IsPartOfProject Value="True"/>
346 <UnitName Value="TBMImport"/>
347 </Unit25>
343 </Units> 348 </Units>
344 </ProjectOptions> 349 </ProjectOptions>
345 <CompilerOptions> 350 <CompilerOptions>
Modifiedhugedatatypes.pas +1−1
@@ -60,7 +60,7 @@ type
60 60
61 TInstrumentType = (itSquare = 0, itWave = 1, itNoise = 2); 61 TInstrumentType = (itSquare = 0, itWave = 1, itNoise = 2);
62 TDutyType = 0..3; 62 TDutyType = 0..3;
63 TSweepType = (stUp, stDown); 63 TSweepType = (stDown = 0, stUp = 1);
64 TStepWidth = (swFifteen, swSeven); 64 TStepWidth = (swFifteen, swSeven);
65 TEnvelopeVolume = 0..15; 65 TEnvelopeVolume = 0..15;
66 TEnvelopeSweepAmount = 0..7; 66 TEnvelopeSweepAmount = 0..7;
Addedtbmimport.pas +299−0
@@ -0,0 +1,299 @@
1 unit TBMImport;
2
3 {$mode ObjFPC}{$H+}
4
5 interface
6
7 uses
8 Classes, SysUtils, song, LazUTF8, HugeDatatypes, Constants, Utils;
9
10 function LoadSongFromTbmStream(Stream: TStream): TSong;
11
12 implementation
13
14 const
15 TBM_EFFECTS: array[0..22] of Byte = (
16 $0, // No effect, this effect column is unset.
17 $B, // `Bxx` begin playing given pattern immediately
18 $0, // `C00` stop playing
19 $D, // `D00` begin playing next pattern immediately
20 $F, // `Fxx` set the tempo
21 $0, // `Txx` play sound effect
22 $C, // `Exx` set the persistent envelope/wave id setting
23 $9, // `Vxx` set persistent duty/wave volume setting
24 $8, // `Ixy` set channel panning setting
25 $0, // `Hxx` set the persistent sweep setting (CH1 only)
26 $E, // `Sxx` note cut delayed by xx frames
27 $7, // `Gxx` note trigger delayed by xx frames
28 $0, // `L00` (lock) stop the sound effect on the current channel
29 $0, // `0xy` arpeggio with semi tones x and y
30 $1, // `1xx` pitch slide up
31 $2, // `2xx` pitch slide down
32 $3, // `3xx` automatic portamento
33 $4, // `4xy` vibrato
34 $0, // `5xx` delay vibrato xx frames on note trigger
35 $0, // `Pxx` fine tuning
36 $1, // `Qxy` note slide up
37 $2, // `Rxy` note slide down
38 $5 // `Jxy` set global volume scale
39 );
40
41 type
42 TTBMHeader = packed record
43 Signature: array[0..11] of Char; // ' TRACKERBOY '
44 VersionMajor: DWord;
45 VersionMinor: DWord;
46 VersionPatch: DWord;
47 MRev: Byte;
48 NRev: Byte;
49 Reserved1: Word;
50
51 Title: array[0..31] of Char;
52 Artist: array[0..31] of Char;
53 Copyright: array[0..31] of Char;
54
55 ICount: Byte;
56 SCount: Byte;
57 WCount: Byte;
58 System: Byte;
59 CustomFrameRate: Word;
60
61 Reserved2: array[0..29] of Byte;
62 end;
63
64 TTBMBlockHeader = packed record
65 Id: array[0..3] of Char;
66 Length: DWord;
67 end;
68
69 TTBMSongFormat = packed record
70 RowsPerBeat: Byte;
71 RowsPerMeasure: Byte;
72 Speed: Byte;
73 PatternCount: Byte;
74 RowsPerTrack: Byte;
75 NumberOfTracks: Word;
76 end;
77
78 TTBMTrackFormat = packed record
79 Channel: Byte;
80 TrackId: Byte;
81 Rows: Byte;
82 end;
83
84 TTBMEffect = packed record
85 EffectType: Byte;
86 Param: Byte;
87 end;
88
89 TTBMTrackRow = packed record
90 Note: Byte;
91 Instrument: Byte;
92 Effects: array[0..2] of TTBMEffect;
93 end;
94
95 TTBMRowFormat = packed record
96 RowNo: Byte;
97 RowData: TTBMTrackRow;
98 end;
99
100 TTBMInstrumentFormat = packed record
101 Channel: Byte;
102 EnvelopeEnabled: Byte;
103 Envelope: Byte;
104 end;
105
106 TTBMSequenceFormat = packed record
107 Length: Word;
108 LoopEnabled: Byte;
109 LoopIndex: Byte;
110 end;
111
112 TStreamHelper = class helper for TStream
113 function ReadLString: String;
114 end;
115
116 function TStreamHelper.ReadLString: String;
117 var
118 Len: Word;
119 Buf: array of byte;
120 begin
121 Self.ReadBuffer(Len, SizeOf(Word));
122 SetLength(Buf, Len);
123 Self.ReadBuffer(Buf[0], Len);
124 Result := UTF8CStringToUTF8String(@Buf[0], Len);
125 end;
126
127 function ConverTBMRow(Row: TTBMTrackRow; RowType: TInstrumentType): TCell;
128 begin
129 with Result do begin
130 Note := (Row.Note-1);
131 if RowType = itNoise then
132 Inc(Note, 4);
133 Instrument := Row.Instrument;
134 EffectCode := TBM_EFFECTS[Row.Effects[0].EffectType];
135 EffectParams.Value := Row.Effects[0].Param;
136
137 if Row.Note = 0 then
138 Note := NO_NOTE;
139
140 if Row.Note = 85 then begin
141 Note := NO_NOTE;
142 EffectCode := $E;
143 EffectParams.Value := $00;
144 end;
145 end;
146 end;
147
148 procedure CleanupPattern(Pat: PPattern);
149 var
150 I: Integer;
151 CurNote: Integer = NO_NOTE;
152 begin
153 for I := Low(TPattern) to High(TPattern) do begin
154 if Pat^[I].Note <> NO_NOTE then
155 CurNote := Pat^[I].Note;
156
157 if (Pat^[I].Instrument <> 0) and (Pat^[I].Note = NO_NOTE) then
158 Pat^[I].Note := CurNote;
159 end;
160 end;
161
162 function LoadSongFromTbmStream(Stream: TStream): TSong;
163 var
164 Header: TTBMHeader;
165 BlockHeader: TTBMBlockHeader;
166 SongFormat: TTBMSongFormat;
167 TrackFormat: TTBMTrackFormat;
168 RowFormat: TTBMRowFormat;
169 I, J: Integer;
170 Pat: PPattern;
171
172 InstId: Byte;
173 InstName: String;
174 InstFormat: TTBMInstrumentFormat;
175 SeqFormat: TTBMSequenceFormat;
176
177 InsType: TInstrumentType;
178 Ins: ^TInstrument;
179
180 EnvReg: TEnvelopeRegister;
181 PitchOffset: Integer;
182 Offs: ShortInt;
183 begin
184 InitializeSong(Result);
185
186 Stream.ReadBuffer(Header, SizeOf(TTBMHeader));
187 with Header do begin
188 writeln('Signature = ', signature);
189 writeln('Title = ', title);
190 writeln('Artist = ', artist);
191 writeln('Copyright = ', copyright);
192 writeln('icount = ', icount);
193 writeln('scount = ', scount);
194 writeln('wcount = ', wcount);
195 end;
196
197 // COMM block
198 Stream.ReadBuffer(BlockHeader, SizeOf(TTBMBlockHeader));
199 Stream.Seek(BlockHeader.Length, soCurrent); // Skip the comment for now
200
201 // SONG block
202 Stream.ReadBuffer(BlockHeader, SizeOf(TTBMBlockHeader));
203 Writeln(Stream.ReadLString); // Song name
204 Stream.ReadBuffer(SongFormat, SizeOf(TTBMSongFormat));
205 Writeln('Song has ', songformat.PatternCount, ' patterns ', songformat.NumberOfTracks, ' tracks');
206 Stream.ReadByte; // ?????????????????
207
208 for I := Low(Result.OrderMatrix) to High(Result.OrderMatrix) do
209 SetLength(Result.OrderMatrix[I], SongFormat.PatternCount+2); // off-by-one error on my part
210
211 for I := 0 to SongFormat.PatternCount do begin
212 Result.OrderMatrix[0, I] := 100 + Stream.ReadByte;
213 Result.OrderMatrix[1, I] := 200 + Stream.ReadByte;
214 Result.OrderMatrix[2, I] := 300 + Stream.ReadByte;
215 Result.OrderMatrix[3, I] := 400 + Stream.ReadByte;
216 end;
217
218 for I := 0 to SongFormat.NumberOfTracks-1 do begin
219 Stream.ReadBuffer(TrackFormat, SizeOf(TTBMTrackFormat));
220
221 case TrackFormat.Channel of
222 0, 1: InsType := itSquare;
223 2: InsType := itWave;
224 3: InsType := itNoise;
225 end;
226
227 Pat := Result.Patterns.GetOrCreateNew(((TrackFormat.Channel+1)*100) + TrackFormat.TrackId);
228 for J := 0 to TrackFormat.Rows do begin
229 Stream.ReadBuffer(RowFormat, SizeOf(TTBMRowFormat));
230 Pat^[RowFormat.RowNo] := ConverTBMRow(RowFormat.RowData, InsType);
231 end;
232 CleanupPattern(Pat);
233 end;
234
235 // INST block
236 for I := 0 to Header.ICount-1 do begin
237 Stream.ReadBuffer(BlockHeader, SizeOf(TTBMBlockHeader));
238 writeln(blockheader.id);
239
240 InstId := Stream.ReadByte;
241 InstName := Stream.ReadLString;
242
243 Stream.ReadBuffer(InstFormat, SizeOf(TTBMInstrumentFormat));
244 case InstFormat.Channel of
245 0, 1: InsType := itSquare;
246 2: InsType := itWave;
247 3: InsType := itNoise;
248 end;
249
250 Ins := @Result.Instruments.All[UnmodInst(InsType, InstId+1)];
251 Ins^.Name := InstName;
252
253 EnvReg.ByteValue := InstFormat.Envelope;
254 Ins^.InitialVolume := EnvReg.InitialVolume;
255 Ins^.VolSweepDirection := TSweepType(EnvReg.Direction);
256 Ins^.VolSweepAmount := EnvReg.SweepNumber;
257
258 Ins^.SubpatternEnabled := True;
259
260 Stream.ReadBuffer(SeqFormat, SizeOf(TTBMSequenceFormat));
261 for J := 0 to SeqFormat.Length-1 do begin
262 Offs := ShortInt(Stream.ReadByte);
263 Ins^.Subpattern[J].Note := MIDDLE_NOTE + Offs;
264 end;
265
266 Stream.ReadBuffer(SeqFormat, SizeOf(TTBMSequenceFormat));
267 Stream.Seek(SeqFormat.Length, soCurrent);
268
269 Stream.ReadBuffer(SeqFormat, SizeOf(TTBMSequenceFormat));
270 PitchOffset := 0;
271 Offs := 0;
272 for J := 0 to SeqFormat.Length-1 do begin
273 Offs := ShortInt(Stream.ReadByte);
274
275 if Offs > PitchOffset then
276 Ins^.Subpattern[J].EffectCode := $1
277 else if Offs < PitchOffset then
278 Ins^.Subpattern[J].EffectCode := $2
279 else
280 Continue;
281
282 Ins^.Subpattern[J].EffectParams.Value := Abs(PitchOffset - Offs);
283 PitchOffset := Offs;
284 end;
285
286 Ins^.Subpattern[SeqFormat.Length].Volume := SeqFormat.LoopIndex;
287
288 Stream.ReadBuffer(SeqFormat, SizeOf(TTBMSequenceFormat));
289 if (InsType = itSquare) and (SeqFormat.Length > 0) then begin
290 Ins^.Duty := Stream.ReadByte;
291 Stream.Seek(SeqFormat.Length-1, soCurrent);
292 end
293 else
294 Stream.Seek(SeqFormat.Length, soCurrent);
295 end;
296 end;
297
298 end.
299
Modifiedtracker.pas +10−8
@@ -10,7 +10,7 @@ uses
10 sound, vars, machine, about_hugetracker, TrackerGrid, lclintf, lmessages, 10 sound, vars, machine, about_hugetracker, TrackerGrid, lclintf, lmessages,
11 Buttons, Grids, DBCtrls, HugeDatatypes, LCLType, Clipbrd, RackCtls, Codegen, 11 Buttons, Grids, DBCtrls, HugeDatatypes, LCLType, Clipbrd, RackCtls, Codegen,
12 SymParser, options, bgrabitmap, effecteditor, RenderToWave, 12 SymParser, options, bgrabitmap, effecteditor, RenderToWave,
13 modimport, mainloop, strutils, Types, Keymap, hUGESettings, vgm; 13 modimport, mainloop, strutils, Types, Keymap, hUGESettings, vgm, TBMImport;
14 14
15 // TODO: Move to config file? 15 // TODO: Move to config file?
16 const 16 const
@@ -2059,16 +2059,18 @@ begin
2059 end; 2059 end;
2060 2060
2061 procedure TfrmTracker.DebugButtonClick(Sender: TObject); 2061 procedure TfrmTracker.DebugButtonClick(Sender: TObject);
2062 var
2063 Stream: TFileStream;
2062 begin 2064 begin
2063 if RenderPreviewROM then begin 2065 DestroySong(Song);
2064 StopPlayback;
2065 ParseSymFile('render/preview.sym');
2066 2066
2067 ExportVGMFile('C:/tmp/yeah.vgm', 'Joogus', 'Nick Faro', 'This song sucks.'); 2067 // TODO: Add error checking
2068 Stream := TFileStream.Create('C:/tmp/n1_navi_V2.tbm', fmOpenRead);
2069 Song := LoadSongFromTbmStream(Stream);
2068 2070
2069 StartPlayback; 2071 Stream.Free;
2070 HaltPlayback; 2072
2071 end; 2073 UpdateUIAfterLoad('Jorgus');
2072 end; 2074 end;
2073 2075
2074 procedure TfrmTracker.DecreaseOctaveActionExecute(Sender: TObject); 2076 procedure TfrmTracker.DecreaseOctaveActionExecute(Sender: TObject);