Commit e759313

Nick Faro committed on
Individual channel export
commit e759313def6a2748062bbf67e6cf04e1b7a2d281 parent 5fc6c0a
3 changed files +130−44
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 +61−20
@@ -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,11 @@ 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 function TfrmRenderToWave.CreateFFMPEGProcess(const DestFilename: String): TProcess;
134 var
135 Proc: TProcess;
136 begin 136 begin
137 z80_reset; 137 Result := TProcess.Create(nil);
138 ResetSound; 138 Result.Executable := 'ffmpeg';
139 enablesound; 139 with Result.Parameters do begin
140
141 FCCallback := nil;
142 load(ConcatPaths([CacheDir, 'render', 'preview.gb']));
143
144 Proc := TProcess.Create(nil);
145 Proc.Executable := 'ffmpeg';
146 with Proc.Parameters do begin
147 // HACK: to prevent ffmpeg from writing to stderr, we disable all output 140 // 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 141 // This is needed because ffmpeg blocks unless you read what it writes
149 Add('-nostats'); 142 Add('-nostats');
@@ -161,13 +154,54 @@ begin
161 Add('-'); 154 Add('-');
162 Add('-f'); 155 Add('-f');
163 Add(GetFFMPEGFormat); 156 Add(GetFFMPEGFormat);
164 Add(Filename); 157 Add(DestFilename);
158 end;
159 Result.Options := [poUsePipes, poNoConsole];
160 end;
161
162 procedure TfrmRenderToWave.ExportWaveToFile(Filename: String);
163 const
164 ChannelSuffixes: array[1..4] of String = ('_pulse1', '_pulse2', '_wave', '_noise');
165 var
166 MixProc: TProcess;
167 ChanProcs: array[1..4] of TProcess;
168 ChanStreams: array[1..4] of TStream;
169 ExportChannels: Boolean;
170 Dir, Base, Ext: String;
171 I: Integer;
172 begin
173 z80_reset;
174 ResetSound;
175 enablesound;
176
177 FCCallback := nil;
178 load(ConcatPaths([CacheDir, 'render', 'preview.gb']));
179
180 ExportChannels := ExportChannelsCheckBox.Checked;
181 for I := 1 to 4 do begin
182 ChanProcs[I] := nil;
183 ChanStreams[I] := nil;
184 end;
185
186 MixProc := CreateFFMPEGProcess(Filename);
187 MixProc.Execute;
188
189 if ExportChannels then begin
190 Dir := ExtractFilePath(Filename);
191 Ext := ExtractFileExt(Filename);
192 Base := ChangeFileExt(ExtractFileName(Filename), '');
193 for I := 1 to 4 do begin
194 ChanProcs[I] := CreateFFMPEGProcess(Dir + Base + ChannelSuffixes[I] + Ext);
195 ChanProcs[I].Execute;
196 ChanStreams[I] := ChanProcs[I].Input;
197 end;
165 end; 198 end;
166 Proc.Options := [poUsePipes, poNoConsole];
167 Proc.Execute;
168 199
169 try 200 try
170 BeginWritingSoundToStream(Proc.Input); 201 if ExportChannels then
202 BeginWritingChannelsToStreams(MixProc.Input, ChanStreams[1], ChanStreams[2], ChanStreams[3], ChanStreams[4])
203 else
204 BeginWritingSoundToStream(MixProc.Input);
171 205
172 if PlayEntireSongRadioButton.Checked then 206 if PlayEntireSongRadioButton.Checked then
173 RenderEntireSong(PlayEntireSongSpinEdit.Value) 207 RenderEntireSong(PlayEntireSongSpinEdit.Value)
@@ -178,9 +212,16 @@ begin
178 212
179 finally 213 finally
180 EndWritingSoundToStream; 214 EndWritingSoundToStream;
181 Proc.CloseInput; 215 MixProc.CloseInput;
182 Proc.WaitOnExit; 216 MixProc.WaitOnExit;
183 Proc.Free; 217 MixProc.Free;
218
219 for I := 1 to 4 do
220 if ChanProcs[I] <> nil then begin
221 ChanProcs[I].CloseInput;
222 ChanProcs[I].WaitOnExit;
223 ChanProcs[I].Free;
224 end;
184 225
185 Panel1.Caption := 'Ready'; 226 Panel1.Caption := 'Ready';
186 end; 227 end;
Modifiedsrc/sound.pas +55−19
@@ -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
@@ -184,7 +185,8 @@ const
184 185
185 var 186 var
186 PlayStream: TSDL_AudioDeviceID; 187 PlayStream: TSDL_AudioDeviceID;
187 bufCycles, bufLVal, bufRVal: integer; 188 bufCycles: integer;
189 bufLVals, bufRVals: array[0..4] of Integer;
188 190
189 sndBuffer: ^Single; 191 sndBuffer: ^Single;
190 sndBytesWritten: Integer; 192 sndBytesWritten: Integer;
@@ -192,7 +194,7 @@ var
192 lfsr: Cardinal = 0; 194 lfsr: Cardinal = 0;
193 195
194 WritingSoundToStream: Boolean; 196 WritingSoundToStream: Boolean;
195 SoundStream: TStream; 197 SoundStreams: array[0..4] of TStream;
196 198
197 procedure ResetSound; 199 procedure ResetSound;
198 var 200 var
@@ -214,14 +216,30 @@ begin
214 end; 216 end;
215 217
216 procedure BeginWritingSoundToStream(Stream: TStream); 218 procedure BeginWritingSoundToStream(Stream: TStream);
219 var
220 I: Integer;
221 begin
222 SoundStreams[0] := Stream;
223 for I := 1 to 4 do SoundStreams[I] := nil;
224 WritingSoundToStream := True;
225 end;
226
227 procedure BeginWritingChannelsToStreams(MixStream, Ch1Stream, Ch2Stream, Ch3Stream, Ch4Stream: TStream);
217 begin 228 begin
229 SoundStreams[0] := MixStream;
230 SoundStreams[1] := Ch1Stream;
231 SoundStreams[2] := Ch2Stream;
232 SoundStreams[3] := Ch3Stream;
233 SoundStreams[4] := Ch4Stream;
218 WritingSoundToStream := True; 234 WritingSoundToStream := True;
219 SoundStream := Stream;
220 end; 235 end;
221 236
222 procedure EndWritingSoundToStream; 237 procedure EndWritingSoundToStream;
238 var
239 I: Integer;
223 begin 240 begin
224 WritingSoundToStream := False; 241 WritingSoundToStream := False;
242 for I := 0 to 4 do SoundStreams[I] := nil;
225 end; 243 end;
226 244
227 procedure StartPlayback; 245 procedure StartPlayback;
@@ -277,8 +295,8 @@ begin
277 295
278 soundEnable := True; 296 soundEnable := True;
279 bufCycles := 0; 297 bufCycles := 0;
280 bufLVal := 0; 298 FillChar(bufLVals, SizeOf(bufLVals), 0);
281 bufRVal := 0; 299 FillChar(bufRVals, SizeOf(bufRVals), 0);
282 end; 300 end;
283 301
284 procedure DisableSound; 302 procedure DisableSound;
@@ -292,35 +310,46 @@ begin
292 soundEnable := False; 310 soundEnable := False;
293 end; 311 end;
294 312
295 procedure SoundDoOut(l, r: Integer; cycles: integer); 313 procedure SoundDoOut(const ls, rs: array of Integer; cycles: integer);
296 var 314 var
315 I: Integer;
297 buf: array[0..1] of Single; 316 buf: array[0..1] of Single;
298 begin 317 begin
299 Inc(bufLVal, l * cycles); 318 for I := 0 to 4 do begin
300 Inc(bufRVal, r * cycles); 319 Inc(bufLVals[I], ls[I] * cycles);
320 Inc(bufRVals[I], rs[I] * cycles);
321 end;
301 Inc(bufCycles, cycles); 322 Inc(bufCycles, cycles);
302 if bufCycles >= sampleCycles then 323 if bufCycles >= sampleCycles then
303 begin 324 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 325 if WritingSoundToStream then begin
311 SoundStream.Write(buf, SizeOf(Single)*2); 326 for I := 0 to 4 do begin
327 if SoundStreams[I] <> nil then begin
328 buf[0] := ((bufRVals[I] div sampleCycles) / 512.0);
329 buf[1] := ((bufLVals[I] div sampleCycles) / 512.0);
330 SoundStreams[I].Write(buf, SizeOf(Single)*2);
331 end;
332 end;
312 end 333 end
313 else begin 334 else begin
335 buf[0] := ((bufRVals[0] div sampleCycles) / 512.0);
336 buf[1] := ((bufLVals[0] div sampleCycles) / 512.0);
314 sndBuffer^ := buf[0]; 337 sndBuffer^ := buf[0];
315 Inc(sndBuffer); 338 Inc(sndBuffer);
316 sndBuffer^ := buf[1]; 339 sndBuffer^ := buf[1];
317 Inc(sndBuffer); 340 Inc(sndBuffer);
318 Inc(sndBytesWritten, SampleSize); 341 Inc(sndBytesWritten, SampleSize);
319 end; 342 end;
343
344 bufCycles := 0;
345 for I := 0 to 4 do begin
346 bufLVals[I] := 0;
347 bufRVals[I] := 0;
348 end;
320 end; 349 end;
321 end; 350 end;
322 351
323 procedure SoundOutBits(l, r: Integer; cycles: integer); 352 procedure SoundOutBits(const ls, rs: array of Integer; cycles: integer);
324 var 353 var
325 left: integer; 354 left: integer;
326 begin 355 begin
@@ -329,10 +358,10 @@ begin
329 while bufCycles + cycles > sampleCycles do 358 while bufCycles + cycles > sampleCycles do
330 begin 359 begin
331 left := sampleCycles - bufCycles; 360 left := sampleCycles - bufCycles;
332 SoundDoOut(l, r, left); 361 SoundDoOut(ls, rs, left);
333 Dec(cycles, left); 362 Dec(cycles, left);
334 end; 363 end;
335 SoundDoOut(l, r, cycles); 364 SoundDoOut(ls, rs, cycles);
336 end; 365 end;
337 366
338 const 367 const
@@ -374,6 +403,7 @@ var
374 n, stage: integer; 403 n, stage: integer;
375 ls: array[1..4] of Integer = (0, 0, 0, 0); 404 ls: array[1..4] of Integer = (0, 0, 0, 0);
376 rs: array[1..4] of Integer = (0, 0, 0, 0); 405 rs: array[1..4] of Integer = (0, 0, 0, 0);
406 chanLs, chanRs: array[0..4] of Integer;
377 l, r: Integer; 407 l, r: Integer;
378 I: Integer; 408 I: Integer;
379 begin 409 begin
@@ -705,7 +735,13 @@ begin
705 SampleBuffers[0].BufferR[SampleBuffers[0].Cursor] := r; 735 SampleBuffers[0].BufferR[SampleBuffers[0].Cursor] := r;
706 SampleBuffers[0].Cursor := (SampleBuffers[0].Cursor + 1) mod SAMPLE_BUFFER_SIZE; 736 SampleBuffers[0].Cursor := (SampleBuffers[0].Cursor + 1) mod SAMPLE_BUFFER_SIZE;
707 737
708 SoundOutBits(l, r, cycles); 738 chanLs[0] := l;
739 chanRs[0] := r;
740 for I := 1 to 4 do begin
741 chanLs[I] := ls[I];
742 chanRs[I] := rs[I];
743 end;
744 SoundOutBits(chanLs, chanRs, cycles);
709 end; 745 end;
710 746
711 begin 747 begin