@@ -5,7 +5,7 @@ unit VGM; |
| 5 |
interface |
5 |
interface |
| 6 |
|
6 |
|
| 7 |
uses |
7 |
uses |
| 8 |
Classes, SysUtils, gvector; |
8 |
Classes, SysUtils, gvector, vars, mainloop, sound, constants; |
| 9 |
|
9 |
|
| 10 |
type |
10 |
type |
| 11 |
// Header for a v1.61 VGM file. |
11 |
// Header for a v1.61 VGM file. |
@@ -83,7 +83,7 @@ type |
| 83 |
var |
83 |
var |
| 84 |
RecordingVGM: Boolean = False; |
84 |
RecordingVGM: Boolean = False; |
| 85 |
|
85 |
|
| 86 |
procedure BeginRecordingVGM(F: String); |
86 |
procedure ExportVGMFile(F: String); |
| 87 |
procedure EndRecordingVGM; |
87 |
procedure EndRecordingVGM; |
| 88 |
|
88 |
|
| 89 |
procedure VGMWriteReg(Reg: Integer; Value: Integer); |
89 |
procedure VGMWriteReg(Reg: Integer; Value: Integer); |
@@ -91,20 +91,60 @@ procedure VGMWait(Amount: Integer); |
| 91 |
|
91 |
|
| 92 |
implementation |
92 |
implementation |
| 93 |
|
93 |
|
|
|
94 |
uses symparser; |
|
|
95 |
|
|
|
96 |
type |
|
|
97 |
// Need to wrap the FC callback in a class because it's declared to be |
|
|
98 |
// "procedure of object" :( |
|
|
99 |
|
|
|
100 |
{ TOrderChecker } |
|
|
101 |
|
|
|
102 |
TOrderChecker = class |
|
|
103 |
procedure OrderCheckCallback; |
|
|
104 |
end; |
|
|
105 |
|
| 94 |
var |
106 |
var |
|
|
107 |
SeenOrders: array of Integer; |
|
|
108 |
OrderChecker: TOrderChecker; |
|
|
109 |
StartOfSong, CurrentOrder: Integer; |
| 95 |
VGMFile: TFileStream; |
110 |
VGMFile: TFileStream; |
| 96 |
CommandBuffer: TCommandVector; |
111 |
CommandBuffer: TCommandVector; |
| 97 |
TotalWaitTicks: Integer; |
112 |
TotalWaitTicks: Integer; |
| 98 |
|
113 |
|
| 99 |
procedure BeginRecordingVGM(F: String); |
114 |
procedure ExportVGMFile(F: String); |
|
|
115 |
var |
|
|
116 |
OldFD, OldFC: TCPUCallback; |
| 100 |
begin |
117 |
begin |
|
|
118 |
OldFD := FDCallback; |
|
|
119 |
OldFC := FCCallback; |
|
|
120 |
FDCallback := nil; |
|
|
121 |
FCCallback := @OrderChecker.OrderCheckCallback; |
|
|
122 |
|
|
|
123 |
z80_reset; |
|
|
124 |
ResetSound; |
|
|
125 |
enablesound; |
|
|
126 |
load('render/preview.gb'); |
|
|
127 |
|
|
|
128 |
SetLength(SeenOrders, PeekSymbol(SYM_ORDER_COUNT) div 2); |
|
|
129 |
StartOfSong := -1; |
|
|
130 |
CurrentOrder := -1; |
|
|
131 |
|
| 101 |
// Due to the annoying format of VGM where the header needs values that can |
132 |
// Due to the annoying format of VGM where the header needs values that can |
| 102 |
// only be computed after rendering the entire file, we store the data in |
133 |
// only be computed after rendering the entire file, we store the data in |
| 103 |
// memory until recording is finished, and dump it all out then. |
134 |
// memory until recording is finished, and dump it all out then. |
| 104 |
// Thankfully VGM files are small enough to fit in RAM. |
135 |
// Thankfully VGM files are small enough to fit in RAM. |
| 105 |
|
136 |
IsWritingVGM := True; |
| 106 |
VGMFile := TFileStream.Create(F, fmOpenWrite); |
137 |
VGMFile := TFileStream.Create(F, fmOpenWrite); |
| 107 |
CommandBuffer.Clear; |
138 |
CommandBuffer.Clear; |
|
|
139 |
|
|
|
140 |
repeat |
|
|
141 |
z80_decode |
|
|
142 |
until StartOfSong <> -1; |
|
|
143 |
|
|
|
144 |
EndRecordingVGM; |
|
|
145 |
|
|
|
146 |
FDCallback := OldFD; |
|
|
147 |
FCCallback := OldFC; |
| 108 |
end; |
148 |
end; |
| 109 |
|
149 |
|
| 110 |
procedure EndRecordingVGM; |
150 |
procedure EndRecordingVGM; |
@@ -112,6 +152,8 @@ var |
| 112 |
Header: TVGMHeader; |
152 |
Header: TVGMHeader; |
| 113 |
Cmd: TVGMCommand; |
153 |
Cmd: TVGMCommand; |
| 114 |
begin |
154 |
begin |
|
|
155 |
IsWritingVGM := False; |
|
|
156 |
|
| 115 |
Header := Default(TVGMHeader); // Zero the header |
157 |
Header := Default(TVGMHeader); // Zero the header |
| 116 |
Header.Vgmident := $56676d20; // "Vgm " |
158 |
Header.Vgmident := $56676d20; // "Vgm " |
| 117 |
Header.Version := $00000161; // 1.61 |
159 |
Header.Version := $00000161; // 1.61 |
@@ -121,6 +163,7 @@ begin |
| 121 |
// TODO: Fill total wait ticks, loop point offset, loop length |
163 |
// TODO: Fill total wait ticks, loop point offset, loop length |
| 122 |
|
164 |
|
| 123 |
VGMFile.WriteBuffer(Header, SizeOf(Header)); |
165 |
VGMFile.WriteBuffer(Header, SizeOf(Header)); |
|
|
166 |
|
| 124 |
for Cmd in CommandBuffer do begin |
167 |
for Cmd in CommandBuffer do begin |
| 125 |
case Cmd.type_ of |
168 |
case Cmd.type_ of |
| 126 |
ctRegWrite: begin |
169 |
ctRegWrite: begin |
@@ -160,7 +203,23 @@ begin |
| 160 |
CommandBuffer.PushBack(Cmd); |
203 |
CommandBuffer.PushBack(Cmd); |
| 161 |
end; |
204 |
end; |
| 162 |
|
205 |
|
|
|
206 |
{ TOrderChecker } |
|
|
207 |
|
|
|
208 |
procedure TOrderChecker.OrderCheckCallback; |
|
|
209 |
var |
|
|
210 |
Ord: Integer; |
|
|
211 |
begin |
|
|
212 |
Ord := (PeekSymbol(SYM_CURRENT_ORDER) div 2); |
|
|
213 |
|
|
|
214 |
if (SeenOrders[Ord] <> 0) and (StartOfSong = -1) then |
|
|
215 |
StartOfSong := Ord; |
|
|
216 |
|
|
|
217 |
Inc(SeenOrders[Ord]); |
|
|
218 |
CurrentOrder := Ord; |
|
|
219 |
end; |
|
|
220 |
|
| 163 |
begin |
221 |
begin |
|
|
222 |
OrderChecker := TOrderChecker.Create; |
| 164 |
CommandBuffer := TCommandVector.Create; |
223 |
CommandBuffer := TCommandVector.Create; |
| 165 |
end. |
224 |
end. |
| 166 |
|
225 |
|