-
Notifications
You must be signed in to change notification settings - Fork 2
/
vgmrecorder.tcl
768 lines (646 loc) · 23.9 KB
/
vgmrecorder.tcl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
namespace eval vgm {
variable active false
variable psg_register
variable opll_register
variable y8950_register
variable opl4_register_wave
variable opl4_register
variable active_fm_register -1
variable start_time 0
variable tick_time 0
variable ticks
variable music_data
variable file_name
variable original_filename
variable directory [file normalize $::env(OPENMSX_USER_DATA)/../vgm_recordings]
variable psg_logged false
variable fm_logged false
variable y8950_logged false
variable moonsound_logged false
variable scc_logged false
variable scc_plus_used
variable watchpoints [list]
variable loop_amount 0
variable position 0
variable auto_next false
variable mbwave_title_hack false
variable mbwave_loop_hack false
variable mbwave_basic_title_hack false
variable supported_chips [list MSX-Music PSG Moonsound MSX-Audio SCC]
set_help_proc vgm_rec [namespace code vgm_rec_help]
proc vgm_rec_help {args} {
switch -- [lindex $args 1] {
"start" {return {VGM recording will be initialised, specify one or more soundchips to record.
Syntax: vgm_rec start <MSX-Audio|MSX-Music|Moonsound|PSG|SCC>
Actual recording will start when audio is detected to avoid silence at the beginning of the recording. This mechanism will only work if the MSX and/or playback routine does not send data to the soundchip when not playing, recording will start immediately in those cases.
}}
"stop" {return {Stop recording and save the data to the openMSX user directory in vgm_recordings. By default the filename will be music0001.vgm, when this exists music0002.vgm etc...
Syntax: vgm_rec stop
}}
"abort" {return {Abort an active recording without saving the data.
Syntax: vgm_rec abort
}}
"next" {return {Stops current recording and starts recording the next track, remembering the chips to record for.
Syntax: vgm_rec next
}}
"auto_next" {return {Enables the auto_next recording; if no data is being sent to the chip for more than 2 seconds, the next recording will be started. Optional argument true/false, defaults to true.
Syntax: vgm_rec auto_next
}}
"prefix" {return {Specify the prefix of the VGM files, instead of the default music.
Syntax: vgm_rec prefix
}}
"enable_hack" {return {Enables one of three hacks; MBWave_basic_title, MBWave_title or MBWave_loop.
Syntax: vgm_rec enable_hack <MBWave_basic_title|MBWave_title|MBWave_loop>
MBWave_basic_title will change the filename written to the track title detected in the openMSX memory when using the MBWave basic driver. MBWave_title will do the same when using MBWave itself. MBWave_loop will insert a vgm pokey command when the tracks loops for the second time. This can be useful when it's hard to find the loop point using the vgmlpfnd tool from vgm_tools. When using vgm_cmp this otherwise useless command will be optimised away.
}}
"disable_hacks" {return {Disable all hacks.
Syntax: vgm_rec disable_hacks
}}
default {return {Record a vgm file from audio playing in openMSX.
Syntax: vgm_rec <sub-command> [arguments if needed]
Where sub-command is one of:
start, stop, abort, next, auto_next, prefix, enable_hack or disable_hacks.
Use 'help vgm_rec <sub-command>' to get more help on specific sub-commands.
}}
}
}
proc little_endian_32 {value} {
binary format i $value
}
proc zeros {value} {
string repeat "\0" $value
}
set_tabcompletion_proc vgm_rec [namespace code tab_vgmrec]
proc tab_vgmrec {args} {
variable supported_chips
if {[lsearch -exact $args "enable_hack"] >= 0} {
concat MBWave_title MBWave_loop MBWave_basic_title
} else {
if {[lsearch -exact $args "start"] >= 0} {
concat $supported_chips
} else {
concat start stop abort next auto_next prefix enable_hack disable_hacks
}
}
}
proc set_next_filename {} {
variable original_filename
variable directory
variable file_name [utils::get_next_numbered_filename $directory $original_filename ".vgm"]
}
proc vgm_rec_set_filename {filename} {
variable original_filename
if {[file extension $filename] eq ".vgm"} {
set filename [file rootname $filename]
}
set original_filename $filename
set_next_filename
}
vgm_rec_set_filename "music"
proc vgm_rec {args} {
variable active
variable auto_next
variable mbwave_title_hack
variable mbwave_loop_hack
variable mbwave_basic_title_hack
variable psg_logged
variable fm_logged
variable y8950_logged
variable moonsound_logged
variable scc_logged
set prefix_index [lsearch -exact $args "prefix"]
if {$prefix_index >= 0} {
if {$prefix_index == ([llength $args] - 1)} {
error "Please specify prefix to use, see 'help vgm_rec prefix'."
}
vgm_rec_set_filename [lindex $args $prefix_index+1]
return
}
set hack_index [lsearch -exact $args "enable_hack"]
if {$hack_index >= 0} {
if {$hack_index == ([llength $args] - 1)} {
error "Please specify hack to activate, use tab completion."
}
foreach a [lrange $args $hack_index+1 end] {
if {[string compare -nocase $a "MBWave_title" ] == 0} {set mbwave_title_hack true} \
elseif {[string compare -nocase $a "MBWave_loop" ] == 0} {set mbwave_loop_hack true} \
elseif {[string compare -nocase $a "MBWave_basic_title"] == 0} {set mbwave_basic_title_hack true} \
else {error "Hack '$a' not recognized, use tab completion."}
}
if {($mbwave_title_hack || $mbwave_loop_hack) && ($mbwave_basic_title_hack)} {
vgm::vgm_disable_hacks
error "Don't combine 'MBWave_basic_title' with other hacks... all hacks disabled to avoid problems."
}
return "Enabled hack(s): $args."
}
if {[lsearch -exact $args "disable_hacks"] >= 0} {
vgm::vgm_disable_hacks
return "Disabled all hacks."
}
set auto_next_index [lsearch -exact $args "auto_next"]
if {$auto_next_index >= 0} {
set param [lindex $args $auto_next_index+1] ;# empty if past end
set paramBool [expr {($param eq "") ? true : bool($param)}]
if {$active && $paramBool} {
error "Auto_next can't be actived during recording, abort/stop the current recording and try again."
}
set auto_next $paramBool
return "[expr {$auto_next ? "Enabled" : "Disabled"}] auto_next feature."
}
if {[lsearch -exact $args "abort"] >= 0} {
return [vgm::vgm_rec_end true]
}
if {[lsearch -exact $args "stop"] >= 0} {
return [vgm::vgm_rec_end false]
}
if {[lsearch -exact $args "next"] >= 0} {
if {!$active} {
error "Not recording now..."
}
return [vgm::vgm_rec_next]
}
set index [lsearch -exact $args "start"]
if {$index >= 0} {
if {$active} {
error "Already recording, please stop it before running start again."
}
if {$index == ([llength $args] - 1)} {
error "Please choose at least one chip to record for, use tab completion."
}
set psg_logged false
set fm_logged false
set y8950_logged false
set moonsound_logged false
set scc_logged false
foreach a [lrange $args $index+1 end] {
if {[string compare -nocase $a "PSG" ] == 0} {set psg_logged true} \
elseif {[string compare -nocase $a "MSX-Music"] == 0} {set fm_logged true} \
elseif {[string compare -nocase $a "MSX-Audio"] == 0} {set y8950_logged true} \
elseif {[string compare -nocase $a "Moonsound"] == 0} {set moonsound_logged true} \
elseif {[string compare -nocase $a "SCC" ] == 0} {set scc_logged true} \
else {
error "Invalid chip to record for specified, use tab completion"
return
}
}
return [vgm::vgm_rec_start]
}
error "Invalid input detected, use tab completion"
}
proc vgm_disable_hacks {} {
variable mbwave_title_hack false
variable mbwave_loop_hack false
variable mbwave_basic_title_hack false
}
proc vgm_rec_start {} {
variable active true
variable auto_next
if {$auto_next} {
vgm::vgm_check_audio_data_written
}
variable mbwave_loop_hack
if {$mbwave_loop_hack} {
vgm::vgm_log_loop_point
}
set_next_filename
variable directory
file mkdir $directory
variable psg_register -1
variable fm_register -1
variable y8950_register -1
variable opl4_register_wave -1
variable opl4_register -1
variable ticks 0
variable music_data ""
variable temp_music_data ""
variable scc_plus_used false
variable watchpoints
variable file_name
set recording_text "VGM recording initiated, start playback now, data will be recorded to $file_name for the following sound chips:"
variable psg_logged
if {$psg_logged} {
lappend watchpoints [debug set_watchpoint write_io 0xA0 {} {vgm::write_psg_address}] \
[debug set_watchpoint write_io 0xA1 {} {vgm::write_psg_data}]
append recording_text " PSG"
}
variable fm_logged
if {$fm_logged} {
lappend watchpoints [debug set_watchpoint write_io 0x7C {} {vgm::write_opll_address}] \
[debug set_watchpoint write_io 0x7D {} {vgm::write_opll_data}]
append recording_text " MSX-Music"
}
variable y8950_logged
if {$y8950_logged} {
lappend watchpoints [debug set_watchpoint write_io 0xC0 {} {vgm::write_y8950_address}] \
[debug set_watchpoint write_io 0xC1 {} {vgm::write_y8950_data}]
# Save the sample RAM as a datablock. If loaded before starting the recording it's fine, if loaded afterward it'll be saved as vgm commands which will be optimised to datablock by the vgmtools
set y8950_ram [concat [machine_info output_port 0xC0] RAM]
if {[lsearch -exact [debug list] $y8950_ram] >= 0} {
set y8950_ram_size [debug size $y8950_ram]
if {$y8950_ram_size > 0} {
append temp_music_data [binary format ccc 0x67 0x66 0x88] \
[little_endian_32 [expr {$y8950_ram_size + 8}]] \
[little_endian_32 $y8950_ram_size] \
[zeros 4] \
[debug read_block $y8950_ram 0 $y8950_ram_size]
}
}
append recording_text " MSX-Audio"
}
# A thing: for wave to work some bits have to be set through FM2. So
# that must be logged. This logs all, but just so you know...
# Another thing: FM data can be used by FM bank 1 and FM bank 2. FM
# data has a mirror however
# So programs can use both ports in different ways; all to FM data,
# FM1->FM-data,FM2->FM-data-mirror, etc. 4 options.
# http://www.msxarchive.nl/pub/msx/docs/programming/opl4tech.txt
variable moonsound_logged
if {$moonsound_logged} {
lappend watchpoints [debug set_watchpoint write_io 0x7E {} {vgm::write_opl4_address_wave}] \
[debug set_watchpoint write_io 0x7F {} {vgm::write_opl4_data_wave}] \
[debug set_watchpoint write_io 0xC4 {} {vgm::write_opl4_address_1}] \
[debug set_watchpoint write_io 0xC5 {} {vgm::write_opl4_data}] \
[debug set_watchpoint write_io 0xC6 {} {vgm::write_opl4_address_2}] \
[debug set_watchpoint write_io 0xC7 {} {vgm::write_opl4_data}]
# Save the sample RAM as a datablock. If loaded before starting the recording it's fine, if loaded afterward it'll be saved as vgm commands which will be optimised to datablock by the vgmtools
set moonsound_ram [concat [machine_info output_port 0x7E] {wave RAM}]
if {[lsearch -exact [debug list] $moonsound_ram] >= 0} {
set moonsound_ram_size [debug size $moonsound_ram]
if {$moonsound_ram_size > 0} {
append temp_music_data [binary format ccc 0x67 0x66 0x87] \
[little_endian_32 [expr {$moonsound_ram_size + 8}]] \
[little_endian_32 $moonsound_ram_size] \
[zeros 4] \
[debug read_block $moonsound_ram 0 $moonsound_ram_size]
# enable OPL4 mode so it's enabled even if recorded vgm data won't do that
append temp_music_data [binary format cccc 0xD0 0x01 0x05 0x03]
}
}
append recording_text " Moondsound"
}
variable scc_logged
if {$scc_logged} {
foreach {ps ss plus} [find_all_scc] {
if {$plus} {
lappend watchpoints [debug set_watchpoint write_mem {0xB800 0xB8AF} "\[watch_in_slot $ps $ss\]" {vgm::scc_plus_data}]
} else {
lappend watchpoints [debug set_watchpoint write_mem {0x9800 0x988F} "\[watch_in_slot $ps $ss\]" {vgm::scc_data}]
}
}
append recording_text " SCC"
}
message $recording_text
return $recording_text
}
proc find_all_scc {} {
set result [list]
for {set ps 0} {$ps < 4} {incr ps} {
for {set ss 0} {$ss < 4} {incr ss} {
set device_list [machine_info slot $ps $ss 2]
if {[llength $device_list] != 0} {
set device [lindex $device_list 0]
set device_info_list [machine_info device $device]
lassign $device_info_list device_info device_sub_info
if {[string match -nocase *scc* $device_info]} {
lappend result $ps $ss 1
}
if {[string match -nocase *scc* $device_sub_info] ||
[string match -nocase manbow2 $device_sub_info] ||
[string match -nocase KonamiUltimateCollection $device_sub_info]} {
lappend result $ps $ss 0
}
}
if {![machine_info issubslotted $ps]} break
}
}
return $result
}
proc write_psg_address {} {
variable psg_register $::wp_last_value
}
proc write_psg_data {} {
variable psg_register
if {$psg_register >= 0 && $psg_register < 14} {
update_time
variable music_data
append music_data [binary format ccc 0xA0 $psg_register $::wp_last_value]
}
}
proc write_opll_address {} {
variable opll_register $::wp_last_value
}
proc write_opll_data {} {
variable opll_register
if {$opll_register >= 0} {
update_time
variable music_data
append music_data [binary format ccc 0x51 $opll_register $::wp_last_value]
}
}
proc write_y8950_address {} {
variable y8950_register $::wp_last_value
}
proc write_y8950_data {} {
variable y8950_register
if {$y8950_register >= 0} {
update_time
variable music_data
append music_data [binary format ccc 0x5C $y8950_register $::wp_last_value]
}
}
proc write_opl4_address_wave {} {
variable opl4_register_wave $::wp_last_value
}
proc write_opl4_data_wave {} {
variable opl4_register_wave
if {$opl4_register_wave >= 0} {
update_time
# VGM spec: Port 0 = FM1, port 1 = FM2, port 2 = Wave. It's
# based on the datasheet A1 & A2 use.
variable music_data
append music_data [binary format cccc 0xD0 0x2 $opl4_register_wave $::wp_last_value]
}
}
proc write_opl4_address_1 {} {
variable opl4_register $::wp_last_value
variable active_fm_register 0
}
proc write_opl4_address_2 {} {
variable opl4_register $::wp_last_value
variable active_fm_register 1
}
proc write_opl4_data {} {
variable opl4_register
variable active_fm_register
if {$opl4_register >= 0} {
update_time
variable music_data
append music_data [binary format cccc 0xD0 $active_fm_register $opl4_register $::wp_last_value]
}
}
proc scc_data {} {
# Thanks ValleyBell, BiFi
# if 9800h is written, waveform channel 1 is set in 9800h - 981fh, 32 bytes
# if 9820h is written, waveform channel 2 is set in 9820h - 983fh, 32 bytes
# if 9840h is written, waveform channel 3 is set in 9840h - 985fh, 32 bytes
# if 9860h is written, waveform channel 4,5 is set in 9860h - 987fh, 32 bytes
# if 9880h is written, frequency channel 1 is set in 9880h - 9881h, 12 bits
# if 9882h is written, frequency channel 2 is set in 9882h - 9883h, 12 bits
# if 9884h is written, frequency channel 3 is set in 9884h - 9885h, 12 bits
# if 9886h is written, frequency channel 4 is set in 9886h - 9887h, 12 bits
# if 9888h is written, frequency channel 5 is set in 9888h - 9889h, 12 bits
# if 988ah is written, volume channel 1 is set, 4 bits
# if 988bh is written, volume channel 2 is set, 4 bits
# if 988ch is written, volume channel 3 is set, 4 bits
# if 988dh is written, volume channel 4 is set, 4 bits
# if 988eh is written, volume channel 5 is set, 4 bits
# if 988fh is written, channels 1-5 on/off, 1 bit
#VGM port format:
#0x00 - waveform
#0x01 - frequency
#0x02 - volume
#0x03 - key on/off
#0x04 - waveform (0x00 used to do SCC access, 0x04 SCC+)
#0x05 - test register
update_time
variable music_data
if {0x9800 <= $::wp_last_address && $::wp_last_address < 0x9880} {
append music_data [binary format cccc 0xD2 0x0 [expr {$::wp_last_address - 0x9800}] $::wp_last_value]
} elseif {0x9880 <= $::wp_last_address && $::wp_last_address < 0x988A} {
append music_data [binary format cccc 0xD2 0x1 [expr {$::wp_last_address - 0x9880}] $::wp_last_value]
} elseif {0x988A <= $::wp_last_address && $::wp_last_address < 0x988F} {
append music_data [binary format cccc 0xD2 0x2 [expr {$::wp_last_address - 0x988A}] $::wp_last_value]
} elseif {$::wp_last_address == 0x988F} {
append music_data [binary format cccc 0xD2 0x3 0x0 $::wp_last_value]
}
}
proc scc_plus_data {} {
# if b800h is written, waveform channel 1 is set in b800h - b81fh, 32 bytes
# if b820h is written, waveform channel 2 is set in b820h - b83fh, 32 bytes
# if b840h is written, waveform channel 3 is set in b840h - b85fh, 32 bytes
# if b860h is written, waveform channel 4 is set in b860h - b87fh, 32 bytes
# if b880h is written, waveform channel 5 is set in b880h - b89fh, 32 bytes
# if b8a0h is written, frequency channel 1 is set in b8a0h - b8a1h, 12 bits
# if b8a2h is written, frequency channel 2 is set in b8a2h - b8a3h, 12 bits
# if b8a4h is written, frequency channel 3 is set in b8a4h - b8a5h, 12 bits
# if b8a6h is written, frequency channel 4 is set in b8a6h - b8a7h, 12 bits
# if b8a8h is written, frequency channel 5 is set in b8a8h - b8a9h, 12 bits
# if b8aah is written, volume channel 1 is set, 4 bits
# if b8abh is written, volume channel 2 is set, 4 bits
# if b8ach is written, volume channel 3 is set, 4 bits
# if b8adh is written, volume channel 4 is set, 4 bits
# if b8aeh is written, volume channel 5 is set, 4 bits
# if b8afh is written, channels 1-5 on/off, 1 bit
#VGM port format:
#0x00 - waveform
#0x01 - frequency
#0x02 - volume
#0x03 - key on/off
#0x04 - waveform (0x00 used to do SCC access, 0x04 SCC+)
#0x05 - test register
update_time
variable music_data
if {0xB800 <= $::wp_last_address && $::wp_last_address < 0xB8A0} {
append music_data [binary format cccc 0xD2 0x4 [expr {$::wp_last_address - 0xB800}] $::wp_last_value]
} elseif {0xB8A0 <= $::wp_last_address && $::wp_last_address < 0xb8aa} {
append music_data [binary format cccc 0xD2 0x1 [expr {$::wp_last_address - 0xB8A0}] $::wp_last_value]
} elseif {0xB8AA <= $::wp_last_address && $::wp_last_address < 0xB8AF} {
append music_data [binary format cccc 0xD2 0x2 [expr {$::wp_last_address - 0xB8AA}] $::wp_last_value]
} elseif {$::wp_last_address == 0xB8AF} {
append music_data [binary format cccc 0xD2 0x3 0x0 $::wp_last_value]
}
variable scc_plus_used true
}
proc update_time {} {
variable start_time
if {$start_time == 0} {
set start_time [machine_info time]
message "VGM recording started, data was written to one of the sound chips recording for."
}
variable tick_time
set tick_time [machine_info time]
set new_ticks [expr {int(($tick_time - $start_time) * 44100)}]
variable ticks
if {$new_ticks < $ticks} {
puts "VGM warning: Tick backstep ($new_ticks -> $ticks)"
set ticks [expr {$new_ticks - 65535}]
}
variable music_data
while {$new_ticks > $ticks} {
set difference [expr {$new_ticks - $ticks}]
set step [expr {$difference > 65535 ? 65535 : $difference}]
incr ticks $step
append music_data [binary format cs 0x61 $step]
}
}
proc vgm_rec_end {abort} {
variable active
if {!$active} {
error "Not recording currently..."
}
# remove all watchpoints that were created
variable watchpoints
foreach watch $watchpoints {
if {[catch {
debug remove_watchpoint $watch
} errorText]} {
puts "Failed to remove watchpoint $watch... using savestates maybe? Continue anyway."
}
}
set watchpoints [list]
if {!$abort} {
update_time
variable tick_time 0
variable music_data
variable temp_music_data
append temp_music_data $music_data [binary format c 0x66]
set header "Vgm "
# file size
append header [little_endian_32 [expr {[string length $temp_music_data] + 0x100 - 4}]]
# VGM version 1.7
append header [little_endian_32 0x161] [zeros 4]
# YM2413 clock
variable fm_logged
if {$fm_logged} {
append header [little_endian_32 3579545]
} else {
append header [zeros 4]
}
append header [zeros 4]
# Number of ticks
variable ticks
append header [little_endian_32 $ticks]
set ticks 0
append header [zeros 24]
# Data starts at offset 0x100
append header [little_endian_32 [expr {0x100 - 0x34}]] [zeros 32]
# Y8950 clock
variable y8950_logged
if {$y8950_logged} {
append header [little_endian_32 3579545]
} else {
append header [zeros 4]
}
append header [zeros 4]
# YMF278B clock
variable moonsound_logged
if {$moonsound_logged} {
append header [little_endian_32 33868800]
} else {
append header [zeros 4]
}
append header [zeros 16]
# AY8910 clock
variable psg_logged
if {$psg_logged} {
append header [little_endian_32 1789773]
} else {
append header [zeros 4]
}
append header [zeros 36]
# SCC clock
variable scc_logged
if {$scc_logged} {
set scc_clock 1789773
variable scc_plus_used
if {$scc_plus_used} {
# enable bit 31 for SCC+ support, that's how it's done
# in VGM I've been told. Thanks Grauw.
set scc_clock [expr {$scc_clock | 1 << 31}]
}
append header [little_endian_32 $scc_clock]
} else {
append header [zeros 4]
}
append header [zeros 96]
variable file_name
variable directory
# Title hacks
variable mbwave_title_hack
variable mbwave_basic_title_hack
if {$mbwave_title_hack || $mbwave_basic_title_hack} {
set title_address [expr {$mbwave_title_hack ? 0xffc6 : 0xc0dc}]
set file_name [string map {/ -} [debug read_block "Main RAM" $title_address 0x32]]
set file_name [string trim $file_name]
set file_name [format %s%s%s%s $directory "/" $file_name ".vgm"]
}
set file_handle [open $file_name "w"]
fconfigure $file_handle -encoding binary -translation binary
puts -nonewline $file_handle $header
puts -nonewline $file_handle $temp_music_data
close $file_handle
set stop_message "VGM recording stopped, wrote data to $file_name."
} else {
set stop_message "VGM recording aborted, no data written..."
}
set active false
variable start_time 0
variable loop_amount 0
message $stop_message
return $stop_message
}
proc vgm_rec_next {} {
variable active
if {!$active} {
variable original_filename "music"
} else {
vgm_rec_end false
}
vgm_rec_start
}
# Generic function to check if audio data is still written when recording is active. If not for a second, assume end recording, and start recording next if this is wanted
proc vgm_check_audio_data_written {} {
variable active
if {!$active} return
variable tick_time
variable auto_next
set now [machine_info time]
if {$tick_time == 0 || $now - $tick_time < 1} {
after time 1 vgm::vgm_check_audio_data_written
} else {
vgm::vgm_rec_end false
if {$auto_next} {
message "auto_next feature active, starting next recording"
vgm::vgm_rec_start
}
}
}
# Loop point logger for MBWave only
proc vgm_log_loop_point {} {
variable watchpoints
lappend watchpoints [debug set_watchpoint write_mem 0x51f7 {} {vgm::vgm_check_loop_point}]
}
proc vgm_check_loop_point {} {
variable start_time
if {$start_time == 0} return
variable position
set position_new [expr {$::wp_last_value == 255 ? 0 : $::wp_last_value}]
if {$position_new < $position} {
after time 1 vgm::vgm_log_loop_in_music_data
}
set position $position_new
}
proc vgm_log_loop_in_music_data {} {
variable start_time
if {$start_time == 0} return
variable loop_amount
incr loop_amount
variable music_data
append music_data [binary format ccc 0xbb 0xbb 0xbb]
if {$loop_amount == 1} {
message "First loop: Track-length in seconds (if not using transposing..): [expr {[machine_info time] - $start_time}]. Marker inserted in VGM file."
}
if {$loop_amount == 2} {
message "Second loop. Marker inserted in VGM file."
}
if {$loop_amount > 2} {
keymatrixdown 7 4
message "Third loop occurred, stop playback."
variable position 0
set loop_amount 0
after time 1 {keymatrixup 7 4}
}
}
namespace export vgm_rec
}
namespace import vgm::*