Commit 1bf039f

Nick Faro committed on
Merge branch 'individual-channel-export' into hUGETracker
commit 1bf039fb8a158688c42975046b31163e33c0ad28 parent 9b945a2parent faef112
4 changed files Diffed against the first of 2 parents. +191−45
Modifiedsrc/hUGEDriver +1−1
@@ -1 +1 @@
1 Subproject commit a3cbd0cea48e6784d7f625066d0300f7cb075926 1 Subproject commit 3200c60c5eec5310105aa406a4e1cd1154875a37
Modifiedsrc/rendertowave.lfm +14−5
@@ -1,11 +1,11 @@
1 object frmRenderToWave: TfrmRenderToWave 1 object frmRenderToWave: TfrmRenderToWave
2 Left = 638 2 Left = 638
3 Height = 279 3 Height = 320
4 Top = 453 4 Top = 453
5 Width = 601 5 Width = 601
6 BorderStyle = bsSingle 6 BorderStyle = bsSingle
7 Caption = 'Render song' 7 Caption = 'Render song'
8 ClientHeight = 279 8 ClientHeight = 320
9 ClientWidth = 601 9 ClientWidth = 601
10 DesignTimePPI = 128 10 DesignTimePPI = 128
11 OnShow = FormShow 11 OnShow = FormShow
@@ -14,7 +14,7 @@ object frmRenderToWave: TfrmRenderToWave
14 object RenderButton: TButton 14 object RenderButton: TButton
15 Left = 19 15 Left = 19
16 Height = 33 16 Height = 33
17 Top = 220 17 Top = 261
18 Width = 352 18 Width = 352
19 Caption = 'Render' 19 Caption = 'Render'
20 Enabled = False 20 Enabled = False
@@ -50,7 +50,7 @@ object frmRenderToWave: TfrmRenderToWave
50 object CancelButton: TButton 50 object CancelButton: TButton
51 Left = 380 51 Left = 380
52 Height = 33 52 Height = 33
53 Top = 220 53 Top = 261
54 Width = 195 54 Width = 195
55 Caption = 'Cancel' 55 Caption = 'Cancel'
56 Enabled = False 56 Enabled = False
@@ -144,9 +144,18 @@ object frmRenderToWave: TfrmRenderToWave
144 object Panel1: TPanel 144 object Panel1: TPanel
145 Left = 21 145 Left = 21
146 Height = 45 146 Height = 45
147 Top = 160 147 Top = 201
148 Width = 556 148 Width = 556
149 Caption = 'Ready' 149 Caption = 'Ready'
150 TabOrder = 9 150 TabOrder = 9
151 end 151 end
152 object ExportChannelsCheckBox: TCheckBox
153 Left = 19
154 Height = 27
155 Top = 160
156 Width = 450
157 Caption = 'Export each channel as a separate file'
158 ParentFont = False
159 TabOrder = 10
160 end
152 end 161 end
Modifiedsrc/rendertowave.pas +97−18
@@ -17,6 +17,7 @@ type
17 17
18 TfrmRenderToWave = class(TForm) 18 TfrmRenderToWave = class(TForm)
19 ComboBox1: TComboBox; 19 ComboBox1: TComboBox;
20 ExportChannelsCheckBox: TCheckBox;
20 Label2: TLabel; 21 Label2: TLabel;
21 Label3: TLabel; 22 Label3: TLabel;
22 Panel1: TPanel; 23 Panel1: TPanel;
@@ -42,6 +43,7 @@ type
42 43
43 function FilenameToFormatIndex: Integer; 44 function FilenameToFormatIndex: Integer;
44 function GetFFMPEGFormat: String; 45 function GetFFMPEGFormat: String;
46 function CreateFFMPEGProcess(const DestFilename: String): TProcess;
45 procedure UpdateUI; 47 procedure UpdateUI;
46 48
47 procedure ExportWaveToFile(Filename: String); 49 procedure ExportWaveToFile(Filename: String);
@@ -130,20 +132,49 @@ begin
130 CancelButton.Enabled := Rendering and not CancelRequested; 132 CancelButton.Enabled := Rendering and not CancelRequested;
131 end; 133 end;
132 134
133 procedure TfrmRenderToWave.ExportWaveToFile(Filename: String); 135 procedure DrainAndWaitForProcesses(const Procs: array of TProcess);
134 var 136 var
135 Proc: TProcess; 137 AllDone: Boolean;
138 I, Avail: Integer;
139 Discard: array[0..4095] of Byte;
136 begin 140 begin
137 z80_reset; 141 repeat
138 ResetSound; 142 AllDone := True;
139 enablesound; 143 for I := Low(Procs) to High(Procs) do begin
140 144 if Procs[I] = nil then Continue;
141 FCCallback := nil; 145
142 load(ConcatPaths([CacheDir, 'render', 'preview.gb'])); 146 if Procs[I].Output <> nil then begin
147 Avail := Procs[I].Output.NumBytesAvailable;
148 while Avail > 0 do begin
149 if Avail > SizeOf(Discard) then Avail := SizeOf(Discard);
150 Procs[I].Output.Read(Discard, Avail);
151 Avail := Procs[I].Output.NumBytesAvailable;
152 end;
153 end;
154
155 if Procs[I].Stderr <> nil then begin
156 Avail := Procs[I].Stderr.NumBytesAvailable;
157 while Avail > 0 do begin
158 if Avail > SizeOf(Discard) then Avail := SizeOf(Discard);
159 Procs[I].Stderr.Read(Discard, Avail);
160 Avail := Procs[I].Stderr.NumBytesAvailable;
161 end;
162 end;
163
164 if Procs[I].Running then AllDone := False;
165 end;
166 if not AllDone then begin
167 Application.ProcessMessages;
168 Sleep(100);
169 end;
170 until AllDone;
171 end;
143 172
144 Proc := TProcess.Create(nil); 173 function TfrmRenderToWave.CreateFFMPEGProcess(const DestFilename: String): TProcess;
145 Proc.Executable := 'ffmpeg'; 174 begin
146 with Proc.Parameters do begin 175 Result := TProcess.Create(nil);
176 Result.Executable := 'ffmpeg';
177 with Result.Parameters do begin
147 // HACK: to prevent ffmpeg from writing to stderr, we disable all output 178 // HACK: to prevent ffmpeg from writing to stderr, we disable all output
148 // This is needed because ffmpeg blocks unless you read what it writes 179 // This is needed because ffmpeg blocks unless you read what it writes
149 Add('-nostats'); 180 Add('-nostats');
@@ -161,13 +192,54 @@ begin
161 Add('-'); 192 Add('-');
162 Add('-f'); 193 Add('-f');
163 Add(GetFFMPEGFormat); 194 Add(GetFFMPEGFormat);
164 Add(Filename); 195 Add(DestFilename);
196 end;
197 Result.Options := [poUsePipes, poNoConsole];
198 end;
199
200 procedure TfrmRenderToWave.ExportWaveToFile(Filename: String);
201 const
202 ChannelSuffixes: array[1..4] of String = ('_pulse1', '_pulse2', '_wave', '_noise');
203 var
204 MixProc: TProcess;
205 ChanProcs: array[1..4] of TProcess;
206 ChanStreams: array[1..4] of TStream;
207 ExportChannels: Boolean;
208 Dir, Base, Ext: String;
209 I: Integer;
210 begin
211 z80_reset;
212 ResetSound;
213 enablesound;
214
215 FCCallback := nil;
216 load(ConcatPaths([CacheDir, 'render', 'preview.gb']));
217
218 ExportChannels := ExportChannelsCheckBox.Checked;
219 for I := 1 to 4 do begin
220 ChanProcs[I] := nil;
221 ChanStreams[I] := nil;
222 end;
223
224 MixProc := CreateFFMPEGProcess(Filename);
225 MixProc.Execute;
226
227 if ExportChannels then begin
228 Dir := ExtractFilePath(Filename);
229 Ext := ExtractFileExt(Filename);
230 Base := ChangeFileExt(ExtractFileName(Filename), '');
231 for I := 1 to 4 do begin
232 ChanProcs[I] := CreateFFMPEGProcess(Dir + Base + ChannelSuffixes[I] + Ext);
233 ChanProcs[I].Execute;
234 ChanStreams[I] := ChanProcs[I].Input;
235 end;
165 end; 236 end;
166 Proc.Options := [poUsePipes, poNoConsole];
167 Proc.Execute;
168 237
169 try 238 try
170 BeginWritingSoundToStream(Proc.Input); 239 if ExportChannels then
240 BeginWritingChannelsToStreams(MixProc.Input, ChanStreams[1], ChanStreams[2], ChanStreams[3], ChanStreams[4])
241 else
242 BeginWritingSoundToStream(MixProc.Input);
171 243
172 if PlayEntireSongRadioButton.Checked then 244 if PlayEntireSongRadioButton.Checked then
173 RenderEntireSong(PlayEntireSongSpinEdit.Value) 245 RenderEntireSong(PlayEntireSongSpinEdit.Value)
@@ -178,9 +250,16 @@ begin
178 250
179 finally 251 finally
180 EndWritingSoundToStream; 252 EndWritingSoundToStream;
181 Proc.CloseInput; 253
182 Proc.WaitOnExit; 254 MixProc.CloseInput;
183 Proc.Free; 255 for I := 1 to 4 do
256 if ChanProcs[I] <> nil then ChanProcs[I].CloseInput;
257
258 DrainAndWaitForProcesses([MixProc, ChanProcs[1], ChanProcs[2], ChanProcs[3], ChanProcs[4]]);
259
260 MixProc.Free;
261 for I := 1 to 4 do
262 if ChanProcs[I] <> nil then ChanProcs[I].Free;
184 263
185 Panel1.Caption := 'Ready'; 264 Panel1.Caption := 'Ready';
186 end; 265 end;
Modifiedsrc/sound.pas +79−21
@@ -154,6 +154,7 @@ procedure ResetSound;
154 procedure SoundUpdate(cycles: integer); 154 procedure SoundUpdate(cycles: integer);
155 155
156 procedure BeginWritingSoundToStream(Stream: TStream); 156 procedure BeginWritingSoundToStream(Stream: TStream);
157 procedure BeginWritingChannelsToStreams(MixStream, Ch1Stream, Ch2Stream, Ch3Stream, Ch4Stream: TStream);
157 procedure EndWritingSoundToStream; 158 procedure EndWritingSoundToStream;
158 159
159 var 160 var
@@ -181,10 +182,12 @@ uses mainloop, vars;
181 const 182 const
182 SampleSize = SizeOf(Single)*2; 183 SampleSize = SizeOf(Single)*2;
183 SampleCycles: LongInt = (8192 * 1024) div PlaybackFrequency; 184 SampleCycles: LongInt = (8192 * 1024) div PlaybackFrequency;
185 StreamFlushBytes = 32768;
184 186
185 var 187 var
186 PlayStream: TSDL_AudioDeviceID; 188 PlayStream: TSDL_AudioDeviceID;
187 bufCycles, bufLVal, bufRVal: integer; 189 bufCycles: integer;
190 bufLVals, bufRVals: array[0..4] of Integer;
188 191
189 sndBuffer: ^Single; 192 sndBuffer: ^Single;
190 sndBytesWritten: Integer; 193 sndBytesWritten: Integer;
@@ -192,7 +195,9 @@ var
192 lfsr: Cardinal = 0; 195 lfsr: Cardinal = 0;
193 196
194 WritingSoundToStream: Boolean; 197 WritingSoundToStream: Boolean;
195 SoundStream: TStream; 198 SoundStreams: array[0..4] of TStream;
199 StreamBuffers: array[0..4] of array[0..StreamFlushBytes-1] of Byte;
200 StreamBufferUsed: array[0..4] of Integer;
196 201
197 procedure ResetSound; 202 procedure ResetSound;
198 var 203 var
@@ -213,15 +218,43 @@ begin
213 end; 218 end;
214 end; 219 end;
215 220
221 procedure FlushStreamBuffer(Idx: Integer);
222 begin
223 if (SoundStreams[Idx] <> nil) and (StreamBufferUsed[Idx] > 0) then
224 SoundStreams[Idx].Write(StreamBuffers[Idx], StreamBufferUsed[Idx]);
225 StreamBufferUsed[Idx] := 0;
226 end;
227
216 procedure BeginWritingSoundToStream(Stream: TStream); 228 procedure BeginWritingSoundToStream(Stream: TStream);
229 var
230 I: Integer;
217 begin 231 begin
232 SoundStreams[0] := Stream;
233 for I := 1 to 4 do SoundStreams[I] := nil;
234 for I := 0 to 4 do StreamBufferUsed[I] := 0;
235 WritingSoundToStream := True;
236 end;
237
238 procedure BeginWritingChannelsToStreams(MixStream, Ch1Stream, Ch2Stream, Ch3Stream, Ch4Stream: TStream);
239 var
240 I: Integer;
241 begin
242 SoundStreams[0] := MixStream;
243 SoundStreams[1] := Ch1Stream;
244 SoundStreams[2] := Ch2Stream;
245 SoundStreams[3] := Ch3Stream;
246 SoundStreams[4] := Ch4Stream;
247 for I := 0 to 4 do StreamBufferUsed[I] := 0;
218 WritingSoundToStream := True; 248 WritingSoundToStream := True;
219 SoundStream := Stream;
220 end; 249 end;
221 250
222 procedure EndWritingSoundToStream; 251 procedure EndWritingSoundToStream;
252 var
253 I: Integer;
223 begin 254 begin
255 for I := 0 to 4 do FlushStreamBuffer(I);
224 WritingSoundToStream := False; 256 WritingSoundToStream := False;
257 for I := 0 to 4 do SoundStreams[I] := nil;
225 end; 258 end;
226 259
227 procedure StartPlayback; 260 procedure StartPlayback;
@@ -277,8 +310,8 @@ begin
277 310
278 soundEnable := True; 311 soundEnable := True;
279 bufCycles := 0; 312 bufCycles := 0;
280 bufLVal := 0; 313 FillChar(bufLVals, SizeOf(bufLVals), 0);
281 bufRVal := 0; 314 FillChar(bufRVals, SizeOf(bufRVals), 0);
282 end; 315 end;
283 316
284 procedure DisableSound; 317 procedure DisableSound;
@@ -292,35 +325,49 @@ begin
292 soundEnable := False; 325 soundEnable := False;
293 end; 326 end;
294 327
295 procedure SoundDoOut(l, r: Integer; cycles: integer); 328 procedure SoundDoOut(const ls, rs: array of Integer; cycles: integer);
296 var 329 var
330 I: Integer;
297 buf: array[0..1] of Single; 331 buf: array[0..1] of Single;
298 begin 332 begin
299 Inc(bufLVal, l * cycles); 333 for I := 0 to 4 do begin
300 Inc(bufRVal, r * cycles); 334 Inc(bufLVals[I], ls[I] * cycles);
335 Inc(bufRVals[I], rs[I] * cycles);
336 end;
301 Inc(bufCycles, cycles); 337 Inc(bufCycles, cycles);
302 if bufCycles >= sampleCycles then 338 if bufCycles >= sampleCycles then
303 begin 339 begin
304 buf[0] := ((bufRVal div sampleCycles) / 512.0);
305 buf[1] := ((bufLVal div sampleCycles) / 512.0);
306 bufCycles := 0;
307 bufLVal := 0;
308 bufRVal := 0;
309
310 if WritingSoundToStream then begin 340 if WritingSoundToStream then begin
311 SoundStream.Write(buf, SizeOf(Single)*2); 341 for I := 0 to 4 do begin
342 if SoundStreams[I] <> nil then begin
343 buf[0] := ((bufRVals[I] div sampleCycles) / 512.0);
344 buf[1] := ((bufLVals[I] div sampleCycles) / 512.0);
345 Move(buf, StreamBuffers[I][StreamBufferUsed[I]], SampleSize);
346 Inc(StreamBufferUsed[I], SampleSize);
347 if StreamBufferUsed[I] >= StreamFlushBytes then
348 FlushStreamBuffer(I);
349 end;
350 end;
312 end 351 end
313 else begin 352 else begin
353 buf[0] := ((bufRVals[0] div sampleCycles) / 512.0);
354 buf[1] := ((bufLVals[0] div sampleCycles) / 512.0);
314 sndBuffer^ := buf[0]; 355 sndBuffer^ := buf[0];
315 Inc(sndBuffer); 356 Inc(sndBuffer);
316 sndBuffer^ := buf[1]; 357 sndBuffer^ := buf[1];
317 Inc(sndBuffer); 358 Inc(sndBuffer);
318 Inc(sndBytesWritten, SampleSize); 359 Inc(sndBytesWritten, SampleSize);
319 end; 360 end;
361
362 bufCycles := 0;
363 for I := 0 to 4 do begin
364 bufLVals[I] := 0;
365 bufRVals[I] := 0;
366 end;
320 end; 367 end;
321 end; 368 end;
322 369
323 procedure SoundOutBits(l, r: Integer; cycles: integer); 370 procedure SoundOutBits(const ls, rs: array of Integer; cycles: integer);
324 var 371 var
325 left: integer; 372 left: integer;
326 begin 373 begin
@@ -329,10 +376,10 @@ begin
329 while bufCycles + cycles > sampleCycles do 376 while bufCycles + cycles > sampleCycles do
330 begin 377 begin
331 left := sampleCycles - bufCycles; 378 left := sampleCycles - bufCycles;
332 SoundDoOut(l, r, left); 379 SoundDoOut(ls, rs, left);
333 Dec(cycles, left); 380 Dec(cycles, left);
334 end; 381 end;
335 SoundDoOut(l, r, cycles); 382 SoundDoOut(ls, rs, cycles);
336 end; 383 end;
337 384
338 const 385 const
@@ -374,6 +421,8 @@ var
374 n, stage: integer; 421 n, stage: integer;
375 ls: array[1..4] of Integer = (0, 0, 0, 0); 422 ls: array[1..4] of Integer = (0, 0, 0, 0);
376 rs: array[1..4] of Integer = (0, 0, 0, 0); 423 rs: array[1..4] of Integer = (0, 0, 0, 0);
424 chanLs, chanRs: array[0..4] of Integer;
425 masterL, masterR: Double;
377 l, r: Integer; 426 l, r: Integer;
378 I: Integer; 427 I: Integer;
379 begin 428 begin
@@ -698,14 +747,23 @@ begin
698 SampleBuffers[I].Cursor := (SampleBuffers[I].Cursor + 1) mod SAMPLE_BUFFER_SIZE; 747 SampleBuffers[I].Cursor := (SampleBuffers[I].Cursor + 1) mod SAMPLE_BUFFER_SIZE;
699 end; 748 end;
700 749
701 l := Trunc((ls[1] + ls[2] + ls[3] + ls[4]) * (((m_iram[$FF24] and 7)+1) / 8)); 750 masterL := ((m_iram[$FF24] and 7) + 1) / 8;
702 r := Trunc((rs[1] + rs[2] + rs[3] + rs[4]) * ((((m_iram[$FF24] shr 4) and 7)+1) / 8)); 751 masterR := (((m_iram[$FF24] shr 4) and 7) + 1) / 8;
752
753 l := Trunc((ls[1] + ls[2] + ls[3] + ls[4]) * masterL);
754 r := Trunc((rs[1] + rs[2] + rs[3] + rs[4]) * masterR);
703 755
704 SampleBuffers[0].BufferL[SampleBuffers[0].Cursor] := l; 756 SampleBuffers[0].BufferL[SampleBuffers[0].Cursor] := l;
705 SampleBuffers[0].BufferR[SampleBuffers[0].Cursor] := r; 757 SampleBuffers[0].BufferR[SampleBuffers[0].Cursor] := r;
706 SampleBuffers[0].Cursor := (SampleBuffers[0].Cursor + 1) mod SAMPLE_BUFFER_SIZE; 758 SampleBuffers[0].Cursor := (SampleBuffers[0].Cursor + 1) mod SAMPLE_BUFFER_SIZE;
707 759
708 SoundOutBits(l, r, cycles); 760 chanLs[0] := l;
761 chanRs[0] := r;
762 for I := 1 to 4 do begin
763 chanLs[I] := Trunc(ls[I] * masterL);
764 chanRs[I] := Trunc(rs[I] * masterR);
765 end;
766 SoundOutBits(chanLs, chanRs, cycles);
709 end; 767 end;
710 768
711 begin 769 begin