Skip to main content

vgmrender/
vgmrender.rs

1//! Rust port of ymfm's `vgmrender` example
2//! (components/ymfm/examples/vgmrender/vgmrender.cpp).
3//!
4//! Renders a VGM chip-command log to a 16-bit stereo WAV file using the
5//! `ymfm-sys` cxx bindings. Compressed (.vgz) files are not supported.
6//! Instead, use a pipe, for example:
7//! `gunzip -c example.vgz | vgmrender - -o example.wav`
8//! Use `-o -` to write the WAV stream to stdout, for example:
9//! `gunzip -c example.vgz | vgmrender - -o - | ffplay -`
10//! The WAV stream is written after rendering is complete.
11
12use std::cell::RefCell;
13use std::env;
14use std::fs;
15use std::io::{self, BufWriter, Read, Write};
16use std::path::Path;
17use std::process::ExitCode;
18use std::rc::Rc;
19
20use ymfm_sys::ffi::{self, AccessClass, ChipType};
21use ymfm_sys::{ChipPtr, InterfaceCallbacks, InterfaceHandler};
22
23/// 32.32 fixed-point emulated time, matching ymfm's `emulated_time`.
24type EmulatedTime = i64;
25
26/// A single active chip instance, matching vgmrender.cpp's `vgm_chip`:
27/// register writes are queued and applied one at a time on each call to
28/// `generate` (matching the pacing assumed by VGM files), and emulation is
29/// resampled from the chip's native rate to the output rate.
30struct ActiveChip {
31    chip: ChipPtr,
32    channels: usize,
33    queue: std::collections::VecDeque<(u32, u8)>,
34    pos: EmulatedTime,
35    step: EmulatedTime,
36    native: Vec<i32>,
37    handler_state: Rc<RefCell<VgmHandlerState>>,
38    pcm_offset: Rc<RefCell<u32>>,
39}
40
41impl ActiveChip {
42    fn new(chip_type: ChipType, clock: u32) -> Self {
43        let state = Rc::new(RefCell::new(VgmHandlerState {
44            data: std::array::from_fn(|_| Vec::new()),
45        }));
46        let pcm_offset = Rc::new(RefCell::new(0u32));
47        let chip = ffi::create_chip_with_callbacks(
48            chip_type,
49            clock,
50            Box::new(InterfaceCallbacks::new(vgm_handler_with_state(Rc::clone(
51                &state,
52            )))),
53        );
54        let channels = chip.channels() as usize;
55        let step: EmulatedTime = 0x1_0000_0000i64 / i64::from(chip.sample_rate());
56        Self {
57            chip,
58            channels,
59            queue: std::collections::VecDeque::new(),
60            pos: 0,
61            step,
62            native: vec![0i32; channels],
63            handler_state: state,
64            pcm_offset,
65        }
66    }
67
68    fn chip_type(&self) -> ChipType {
69        self.chip.chip_type()
70    }
71
72    /// Queue a register write. `reg` encodes the register number in the low
73    /// byte and the port index in bits 8-9. Applied on the next call to
74    /// `generate`.
75    fn write(&mut self, reg: u32, data: u8) {
76        self.queue.push_back((reg, data));
77    }
78
79    fn write_data(&mut self, access: AccessClass, base: u32, data: &[u8]) {
80        let mut state = self.handler_state.borrow_mut();
81        for (index, value) in data.iter().copied().enumerate() {
82            write_byte(&mut state, access, base + index as u32, value);
83        }
84    }
85
86    fn seek_pcm(&mut self, pos: u32) {
87        *self.pcm_offset.borrow_mut() = pos;
88    }
89
90    fn read_pcm(&mut self) -> u8 {
91        let mut offset = self.pcm_offset.borrow_mut();
92        let state = self.handler_state.borrow();
93        let value = read_byte(&state, AccessClass::Pcm, *offset);
94        *offset = offset.saturating_add(1);
95        value
96    }
97
98    /// Advance emulation up to `output_start` and accumulate one stereo
99    /// sample into `buffer[0]` (left) / `buffer[1]` (right), matching
100    /// vgmrender.cpp's `vgm_chip::generate`.
101    fn generate(
102        &mut self,
103        output_start: EmulatedTime,
104        output_step: EmulatedTime,
105        buffer: &mut [i32],
106    ) {
107        let _ = output_step;
108
109        // dequeue at most one pending register write per output sample
110        if let Some((reg, data)) = self.queue.pop_front() {
111            let addr1 = 2 * ((reg >> 8) & 3);
112            let data1 = (reg & 0xff) as u8;
113            let addr2 = addr1
114                + if self.chip_type() == ChipType::Ym2149 {
115                    2
116                } else {
117                    1
118                };
119            self.chip.pin_mut().write(addr1, data1);
120            self.chip.pin_mut().write(addr2, data);
121        }
122
123        // generate at the chip's native rate, catching up to output_start
124        while self.pos <= output_start {
125            self.chip.pin_mut().generate(&mut self.native);
126            self.pos += self.step;
127        }
128
129        let channels = self.channels;
130        let out = &self.native;
131        match self.chip.chip_type() {
132            ChipType::Ym2203 => {
133                let sum = out[0] + out[1 % channels] + out[2 % channels] + out[3 % channels];
134                buffer[0] += sum;
135                buffer[1] += sum;
136            }
137            ChipType::Ym2608 | ChipType::Ym2610 => {
138                buffer[0] += out[0] + out[2 % channels];
139                buffer[1] += out[1 % channels] + out[2 % channels];
140            }
141            ChipType::Ymf278B => {
142                buffer[0] += out[4 % channels];
143                buffer[1] += out[5 % channels];
144            }
145            _ if channels == 1 => {
146                buffer[0] += out[0];
147                buffer[1] += out[0];
148            }
149            _ => {
150                buffer[0] += out[0];
151                buffer[1] += out[1 % channels];
152            }
153        }
154    }
155}
156
157/// Read a little-endian u32 from `buffer` at `offset`, advancing `offset`.
158fn read_u32(buffer: &[u8], offset: &mut usize) -> u32 {
159    let value = u32::from_le_bytes(buffer[*offset..*offset + 4].try_into().unwrap());
160    *offset += 4;
161    value
162}
163
164/// Find the `index`-th active chip of the given category (0-based), matching
165/// vgmrender.cpp's `find_chip`.
166fn find_chip(
167    chips: &mut [ActiveChip],
168    category: ChipType,
169    mut index: u8,
170) -> Option<&mut ActiveChip> {
171    for chip in chips.iter_mut() {
172        if chip.chip_type() == category {
173            if index == 0 {
174                return Some(chip);
175            }
176            index -= 1;
177        }
178    }
179    None
180}
181
182/// Write a register to the `index`-th active chip of the given category, matching
183/// vgmrender.cpp's `write_chip`.
184fn write_chip(chips: &mut [ActiveChip], category: ChipType, index: u8, reg: u32, data: u8) {
185    if let Some(chip) = find_chip(chips, category, index) {
186        chip.write(reg, data);
187    }
188}
189
190/// Create 1 or 2 instances of the given chip type, matching vgmrender.cpp's
191/// `add_chips` (bit 30 of the clock value requests a second chip instance).
192fn add_chips(chips: &mut Vec<ActiveChip>, chip_type: ChipType, clock: u32, name: &str) {
193    let clock_value = clock & 0x3fff_ffff;
194    let num_chips = if clock & 0x4000_0000 != 0 { 2 } else { 1 };
195    eprintln!(
196        "Adding {}{} @ {}Hz",
197        if num_chips == 2 { "2 x " } else { "" },
198        name,
199        clock_value
200    );
201    for _ in 0..num_chips {
202        chips.push(ActiveChip::new(chip_type, clock_value));
203    }
204
205    if chip_type == ChipType::Ym2608 {
206        match fs::read("ym2608_adpcm_rom.bin") {
207            Ok(rom) => {
208                for chip in chips
209                    .iter_mut()
210                    .filter(|c| c.chip_type() == ChipType::Ym2608)
211                {
212                    chip.write_data(AccessClass::AdpcmA, 0, &rom);
213                }
214            }
215            Err(_) => eprintln!("Warning: YM2608 enabled but ym2608_adpcm_rom.bin not found"),
216        }
217    }
218}
219
220/// Load ROM data for a data-block command, matching vgmrender.cpp's
221/// `add_rom_data`: reads a (length, start) pair, then writes the remaining
222/// `size` bytes to every active chip of the given category.
223fn add_rom_data(
224    chips: &mut [ActiveChip],
225    category: ChipType,
226    access: AccessClass,
227    buffer: &[u8],
228    mut local_offset: usize,
229    size: u32,
230) {
231    let _length = read_u32(buffer, &mut local_offset);
232    let start = read_u32(buffer, &mut local_offset);
233    for index in 0..2u8 {
234        if let Some(chip) = find_chip(chips, category, index) {
235            chip.write_data(
236                access,
237                start,
238                &buffer[local_offset..local_offset + size as usize],
239            );
240        }
241    }
242}
243
244/// Parse the VGM header, creating any chips we recognize, and return the
245/// offset at which the command stream begins.
246fn parse_header(buffer: &[u8]) -> (u32, Vec<ActiveChip>) {
247    let mut chips = Vec::new();
248    let mut offset = 4usize;
249
250    // +04: total size (informational only; buffer already holds the whole file)
251    let _size = read_u32(buffer, &mut offset);
252
253    // +08: version
254    let version = read_u32(buffer, &mut offset);
255    if version > 0x171 {
256        eprintln!("Warning: version > 1.71 detected, some things may not work");
257    }
258
259    // +0C: SN76489 clock
260    let clock = read_u32(buffer, &mut offset);
261    if clock != 0 {
262        eprintln!("Warning: clock for SN76489 specified ({clock}), but not supported");
263    }
264
265    // +10: YM2413 clock
266    let clock = read_u32(buffer, &mut offset);
267    if clock != 0 {
268        add_chips(&mut chips, ChipType::Ym2413, clock, "YM2413");
269    }
270
271    // +14: GD3 offset / +18: total # samples / +1C: loop offset / +20: loop # samples
272    // +24: rate / +28: SN76489 feedback/shift/flags
273    let _gd3_offset = read_u32(buffer, &mut offset);
274    let _total_samples = read_u32(buffer, &mut offset);
275    let _loop_offset = read_u32(buffer, &mut offset);
276    let _loop_samples = read_u32(buffer, &mut offset);
277    let _rate = read_u32(buffer, &mut offset);
278    let _sn76489_extra = read_u32(buffer, &mut offset);
279
280    // +2C: YM2612 clock
281    let clock = read_u32(buffer, &mut offset);
282    if version >= 0x110 && clock != 0 {
283        add_chips(&mut chips, ChipType::Ym2612, clock, "YM2612");
284    }
285
286    // +30: YM2151 clock
287    let clock = read_u32(buffer, &mut offset);
288    if version >= 0x110 && clock != 0 {
289        add_chips(&mut chips, ChipType::Ym2151, clock, "YM2151");
290    }
291
292    // +34: VGM data offset
293    let data_offset = read_u32(buffer, &mut offset);
294    let data_start = if version < 0x150 {
295        0x40
296    } else {
297        data_offset.wrapping_add(offset as u32 - 4)
298    };
299
300    // beyond this point, bail out early (returning what we have so far) if the
301    // header is too short to contain the next field
302    macro_rules! next_field {
303        () => {{
304            if offset + 4 > data_start as usize {
305                return (data_start, chips);
306            }
307            read_u32(buffer, &mut offset)
308        }};
309    }
310
311    // +38: Sega PCM clock
312    let clock = read_u32(buffer, &mut offset);
313    if version >= 0x151 && clock != 0 {
314        eprintln!("Warning: clock for Sega PCM specified, but not supported");
315    }
316
317    // +3C: Sega PCM interface register
318    let _sega_pcm_if = read_u32(buffer, &mut offset);
319
320    // +40: RF5C68 clock
321    let clock = next_field!();
322    if version >= 0x151 && clock != 0 {
323        eprintln!("Warning: clock for RF5C68 specified, but not supported");
324    }
325
326    // +44: YM2203 clock
327    let clock = next_field!();
328    if version >= 0x151 && clock != 0 {
329        add_chips(&mut chips, ChipType::Ym2203, clock, "YM2203");
330    }
331
332    // +48: YM2608 clock
333    let clock = next_field!();
334    if version >= 0x151 && clock != 0 {
335        add_chips(&mut chips, ChipType::Ym2608, clock, "YM2608");
336    }
337
338    // +4C: YM2610/2610B clock
339    let clock = next_field!();
340    if version >= 0x151 && clock != 0 {
341        if clock & 0x8000_0000 != 0 {
342            add_chips(&mut chips, ChipType::Ym2610B, clock, "YM2610B");
343        } else {
344            add_chips(&mut chips, ChipType::Ym2610, clock, "YM2610");
345        }
346    }
347
348    // +50: YM3812 clock
349    let clock = next_field!();
350    if version >= 0x151 && clock != 0 {
351        add_chips(&mut chips, ChipType::Ym3812, clock, "YM3812");
352    }
353
354    // +54: YM3526 clock
355    let clock = next_field!();
356    if version >= 0x151 && clock != 0 {
357        add_chips(&mut chips, ChipType::Ym3526, clock, "YM3526");
358    }
359
360    // +58: Y8950 clock
361    let clock = next_field!();
362    if version >= 0x151 && clock != 0 {
363        add_chips(&mut chips, ChipType::Y8950, clock, "Y8950");
364    }
365
366    // +5C: YMF262 clock
367    let clock = next_field!();
368    if version >= 0x151 && clock != 0 {
369        add_chips(&mut chips, ChipType::Ymf262, clock, "YMF262");
370    }
371
372    // +60: YMF278B clock
373    let clock = next_field!();
374    if version >= 0x151 && clock != 0 {
375        add_chips(&mut chips, ChipType::Ymf278B, clock, "YMF278B");
376    }
377
378    // +64: YMF271 clock
379    let clock = next_field!();
380    if version >= 0x151 && clock != 0 {
381        eprintln!("Warning: clock for YMF271 specified, but not supported");
382    }
383
384    // +68: YMF280B clock
385    let clock = next_field!();
386    if version >= 0x151 && clock != 0 {
387        eprintln!("Warning: clock for YMF280B specified, but not supported");
388    }
389
390    // +6C: RF5C164 clock
391    let clock = next_field!();
392    if version >= 0x151 && clock != 0 {
393        eprintln!("Warning: clock for RF5C164 specified, but not supported");
394    }
395
396    // +70: PWM clock
397    let clock = next_field!();
398    if version >= 0x151 && clock != 0 {
399        eprintln!("Warning: clock for PWM specified, but not supported");
400    }
401
402    // +74: AY8910 clock
403    let clock = next_field!();
404    if version >= 0x151 && clock != 0 {
405        eprintln!("Warning: clock for AY8910 specified, substituting YM2149");
406        add_chips(&mut chips, ChipType::Ym2149, clock, "YM2149");
407    }
408
409    // +78: AY8910 flags
410    let _ay8910_flags = next_field!();
411
412    // +7C: volume / loop info
413    let volume_info = next_field!();
414    if volume_info & 0xff != 0 {
415        let modifier = 2f64.powf(f64::from(volume_info & 0xff) / 0x20 as f64);
416        eprintln!(
417            "Volume modifier: {:02X} (={})",
418            volume_info & 0xff,
419            modifier as i32
420        );
421    }
422
423    // +80: GameBoy DMG clock
424    let clock = next_field!();
425    if version >= 0x161 && clock != 0 {
426        eprintln!("Warning: clock for GameBoy DMG specified, but not supported");
427    }
428
429    // +84: NES APU clock
430    let clock = next_field!();
431    if version >= 0x161 && clock != 0 {
432        eprintln!("Warning: clock for NES APU specified, but not supported");
433    }
434
435    // +88: MultiPCM clock
436    let clock = next_field!();
437    if version >= 0x161 && clock != 0 {
438        eprintln!("Warning: clock for MultiPCM specified, but not supported");
439    }
440
441    // +8C: uPD7759 clock
442    let clock = next_field!();
443    if version >= 0x161 && clock != 0 {
444        eprintln!("Warning: clock for uPD7759 specified, but not supported");
445    }
446
447    // +90: OKIM6258 clock
448    let clock = next_field!();
449    if version >= 0x161 && clock != 0 {
450        eprintln!("Warning: clock for OKIM6258 specified, but not supported");
451    }
452
453    // +94: OKIM6258 Flags / K054539 Flags / C140 Chip Type / reserved
454    let _flags = next_field!();
455
456    // +98: OKIM6295 clock
457    let clock = next_field!();
458    if version >= 0x161 && clock != 0 {
459        eprintln!("Warning: clock for OKIM6295 specified, but not supported");
460    }
461
462    // +9C: K051649 clock
463    let clock = next_field!();
464    if version >= 0x161 && clock != 0 {
465        eprintln!("Warning: clock for K051649 specified, but not supported");
466    }
467
468    // +A0: K054539 clock
469    let clock = next_field!();
470    if version >= 0x161 && clock != 0 {
471        eprintln!("Warning: clock for K054539 specified, but not supported");
472    }
473
474    // +A4: HuC6280 clock
475    let clock = next_field!();
476    if version >= 0x161 && clock != 0 {
477        eprintln!("Warning: clock for HuC6280 specified, but not supported");
478    }
479
480    // +A8: C140 clock
481    let clock = next_field!();
482    if version >= 0x161 && clock != 0 {
483        eprintln!("Warning: clock for C140 specified, but not supported");
484    }
485
486    // +AC: K053260 clock
487    let clock = next_field!();
488    if version >= 0x161 && clock != 0 {
489        eprintln!("Warning: clock for K053260 specified, but not supported");
490    }
491
492    // +B0: Pokey clock
493    let clock = next_field!();
494    if version >= 0x161 && clock != 0 {
495        eprintln!("Warning: clock for Pokey specified, but not supported");
496    }
497
498    // +B4: QSound clock
499    let clock = next_field!();
500    if version >= 0x161 && clock != 0 {
501        eprintln!("Warning: clock for QSound specified, but not supported");
502    }
503
504    // +B8: SCSP clock
505    let clock = next_field!();
506    if version >= 0x171 && clock != 0 {
507        eprintln!("Warning: clock for SCSP specified, but not supported");
508    }
509
510    // +BC: extra header offset
511    let _extra_header = next_field!();
512
513    // +C0: WonderSwan clock
514    let clock = next_field!();
515    if version >= 0x171 && clock != 0 {
516        eprintln!("Warning: clock for WonderSwan specified, but not supported");
517    }
518
519    // +C4: VSU clock
520    let clock = next_field!();
521    if version >= 0x171 && clock != 0 {
522        eprintln!("Warning: clock for VSU specified, but not supported");
523    }
524
525    // +C8: SAA1099 clock
526    let clock = next_field!();
527    if version >= 0x171 && clock != 0 {
528        eprintln!("Warning: clock for SAA1099 specified, but not supported");
529    }
530
531    // +CC: ES5503 clock
532    let clock = next_field!();
533    if version >= 0x171 && clock != 0 {
534        eprintln!("Warning: clock for ES5503 specified, but not supported");
535    }
536
537    // +D0: ES5505/ES5506 clock
538    let clock = next_field!();
539    if version >= 0x171 && clock != 0 {
540        eprintln!("Warning: clock for ES5505/ES5506 specified, but not supported");
541    }
542
543    // +D4: ES5503 output channels / ES5505/ES5506 amount of output channels / C352 clock divider
544    let _es_channels = next_field!();
545
546    // +D8: X1-010 clock
547    let clock = next_field!();
548    if version >= 0x171 && clock != 0 {
549        eprintln!("Warning: clock for X1-010 specified, but not supported");
550    }
551
552    // +DC: C352 clock
553    let clock = next_field!();
554    if version >= 0x171 && clock != 0 {
555        eprintln!("Warning: clock for C352 specified, but not supported");
556    }
557
558    // +E0: GA20 clock
559    let clock = next_field!();
560    if version >= 0x171 && clock != 0 {
561        eprintln!("Warning: clock for GA20 specified, but not supported");
562    }
563
564    (data_start, chips)
565}
566
567/// Interpret the VGM command stream, driving all active chips and
568/// accumulating interleaved stereo samples, matching vgmrender.cpp's
569/// `generate_all`.
570fn generate_all(
571    buffer: &[u8],
572    data_start: u32,
573    output_rate: u32,
574    chips: &mut [ActiveChip],
575) -> Vec<i32> {
576    let mut wav_buffer = Vec::new();
577    let mut offset = data_start as usize;
578    let mut done = false;
579    let output_step: EmulatedTime = 0x1_0000_0000i64 / i64::from(output_rate);
580    let mut output_pos: EmulatedTime = 0;
581
582    while !done && offset < buffer.len() {
583        let mut delay: i32 = 0;
584        let cmd = buffer[offset];
585        offset += 1;
586
587        match cmd {
588            // register writes: dd to register aa on the selected chip/port
589            0x51 | 0xa1 => {
590                write_chip(
591                    chips,
592                    ChipType::Ym2413,
593                    cmd >> 7,
594                    u32::from(buffer[offset]),
595                    buffer[offset + 1],
596                );
597                offset += 2;
598            }
599            0x52 | 0xa2 => {
600                write_chip(
601                    chips,
602                    ChipType::Ym2612,
603                    cmd >> 7,
604                    u32::from(buffer[offset]),
605                    buffer[offset + 1],
606                );
607                offset += 2;
608            }
609            0x53 | 0xa3 => {
610                write_chip(
611                    chips,
612                    ChipType::Ym2612,
613                    cmd >> 7,
614                    u32::from(buffer[offset]) | 0x100,
615                    buffer[offset + 1],
616                );
617                offset += 2;
618            }
619            0x54 | 0xa4 => {
620                write_chip(
621                    chips,
622                    ChipType::Ym2151,
623                    cmd >> 7,
624                    u32::from(buffer[offset]),
625                    buffer[offset + 1],
626                );
627                offset += 2;
628            }
629            0x55 | 0xa5 => {
630                write_chip(
631                    chips,
632                    ChipType::Ym2203,
633                    cmd >> 7,
634                    u32::from(buffer[offset]),
635                    buffer[offset + 1],
636                );
637                offset += 2;
638            }
639            0x56 | 0xa6 => {
640                write_chip(
641                    chips,
642                    ChipType::Ym2608,
643                    cmd >> 7,
644                    u32::from(buffer[offset]),
645                    buffer[offset + 1],
646                );
647                offset += 2;
648            }
649            0x57 | 0xa7 => {
650                write_chip(
651                    chips,
652                    ChipType::Ym2608,
653                    cmd >> 7,
654                    u32::from(buffer[offset]) | 0x100,
655                    buffer[offset + 1],
656                );
657                offset += 2;
658            }
659            0x58 | 0xa8 => {
660                write_chip(
661                    chips,
662                    ChipType::Ym2610,
663                    cmd >> 7,
664                    u32::from(buffer[offset]),
665                    buffer[offset + 1],
666                );
667                offset += 2;
668            }
669            0x59 | 0xa9 => {
670                write_chip(
671                    chips,
672                    ChipType::Ym2610,
673                    cmd >> 7,
674                    u32::from(buffer[offset]) | 0x100,
675                    buffer[offset + 1],
676                );
677                offset += 2;
678            }
679            0x5a | 0xaa => {
680                write_chip(
681                    chips,
682                    ChipType::Ym3812,
683                    cmd >> 7,
684                    u32::from(buffer[offset]),
685                    buffer[offset + 1],
686                );
687                offset += 2;
688            }
689            0x5b | 0xab => {
690                write_chip(
691                    chips,
692                    ChipType::Ym3526,
693                    cmd >> 7,
694                    u32::from(buffer[offset]),
695                    buffer[offset + 1],
696                );
697                offset += 2;
698            }
699            0x5c | 0xac => {
700                write_chip(
701                    chips,
702                    ChipType::Y8950,
703                    cmd >> 7,
704                    u32::from(buffer[offset]),
705                    buffer[offset + 1],
706                );
707                offset += 2;
708            }
709            0x5e | 0xae => {
710                write_chip(
711                    chips,
712                    ChipType::Ymf262,
713                    cmd >> 7,
714                    u32::from(buffer[offset]),
715                    buffer[offset + 1],
716                );
717                offset += 2;
718            }
719            0x5f | 0xaf => {
720                write_chip(
721                    chips,
722                    ChipType::Ymf262,
723                    cmd >> 7,
724                    u32::from(buffer[offset]) | 0x100,
725                    buffer[offset + 1],
726                );
727                offset += 2;
728            }
729
730            // wait n samples, n = 0..65535
731            0x61 => {
732                delay = i32::from(buffer[offset]) | (i32::from(buffer[offset + 1]) << 8);
733                offset += 2;
734            }
735            // wait 735 samples (60th of a second)
736            0x62 => delay = 735,
737            // wait 882 samples (50th of a second)
738            0x63 => delay = 882,
739            // end of sound data
740            0x66 => done = true,
741
742            // data block
743            0x67 => {
744                let marker = buffer[offset];
745                offset += 1;
746                if marker == 0x66 {
747                    let dtype = buffer[offset];
748                    offset += 1;
749                    let size = read_u32(buffer, &mut offset);
750                    let local_offset = offset;
751
752                    match dtype {
753                        // uncompressed data for use with associated commands: not supported
754                        0x01..=0x07 => {}
755
756                        // YM2612 PCM data for use with associated commands
757                        0x00 => {
758                            if let Some(chip) = find_chip(chips, ChipType::Ym2612, 0) {
759                                let len = (size as usize).saturating_sub(8);
760                                chip.write_data(
761                                    AccessClass::Pcm,
762                                    0,
763                                    &buffer[local_offset..local_offset + len],
764                                );
765                            }
766                        }
767
768                        // YM2610 ADPCM ROM data
769                        0x82 => add_rom_data(
770                            chips,
771                            ChipType::Ym2610,
772                            AccessClass::AdpcmA,
773                            buffer,
774                            local_offset,
775                            size - 8,
776                        ),
777                        // YM2608 DELTA-T ROM data
778                        0x81 => add_rom_data(
779                            chips,
780                            ChipType::Ym2608,
781                            AccessClass::AdpcmB,
782                            buffer,
783                            local_offset,
784                            size - 8,
785                        ),
786                        // YM2610 DELTA-T ROM data
787                        0x83 => add_rom_data(
788                            chips,
789                            ChipType::Ym2610,
790                            AccessClass::AdpcmB,
791                            buffer,
792                            local_offset,
793                            size - 8,
794                        ),
795                        // YMF278B ROM/RAM data
796                        0x84 | 0x87 => add_rom_data(
797                            chips,
798                            ChipType::Ymf278B,
799                            AccessClass::Pcm,
800                            buffer,
801                            local_offset,
802                            size - 8,
803                        ),
804                        // Y8950 DELTA-T ROM data
805                        0x88 => add_rom_data(
806                            chips,
807                            ChipType::Y8950,
808                            AccessClass::AdpcmB,
809                            buffer,
810                            local_offset,
811                            size - 8,
812                        ),
813
814                        // ROM data for chips we don't support
815                        0x80 | 0x85 | 0x86 | 0x89..=0x93 => {}
816                        // RAM writes: not supported
817                        0xc0..=0xc2 | 0xe0 | 0xe1 => {}
818
819                        other => {
820                            if (0x40..0x7f).contains(&other) {
821                                eprintln!("Compressed data block not supported");
822                            } else {
823                                eprintln!("Unknown data block type {other:#04X}");
824                            }
825                        }
826                    }
827                    offset += size as usize;
828                }
829            }
830
831            // PCM RAM write
832            0x68 => eprintln!("68: PCM RAM write"),
833
834            // AY8910, write value dd to register aa
835            0xa0 => {
836                write_chip(
837                    chips,
838                    ChipType::Ym2149,
839                    buffer[offset] >> 7,
840                    u32::from(buffer[offset] & 0x7f),
841                    buffer[offset + 1],
842                );
843                offset += 2;
844            }
845
846            // pp aa dd: YMF278B, port pp, write value dd to register aa
847            0xd0 => {
848                let reg = (u32::from(buffer[offset] & 0x7f) << 8) | u32::from(buffer[offset + 1]);
849                write_chip(
850                    chips,
851                    ChipType::Ymf278B,
852                    buffer[offset] >> 7,
853                    reg,
854                    buffer[offset + 2],
855                );
856                offset += 3;
857            }
858
859            0x70..=0x7f => delay = i32::from(cmd & 15) + 1,
860
861            0x80..=0x8f => {
862                if let Some(chip) = find_chip(chips, ChipType::Ym2612, 0) {
863                    let sample = chip.read_pcm();
864                    chip.write(0x2a, sample);
865                }
866                delay = i32::from(cmd & 15);
867            }
868
869            // ignored, consume one byte
870            0x30..=0x3f | 0x4f | 0x50 => offset += 1,
871
872            // ignored, consume two bytes
873            0x40..=0x4e | 0x5d | 0xb0..=0xbf => offset += 2,
874
875            // ignored, consume three bytes
876            0xc0..=0xc8 | 0xc9..=0xcf | 0xd1..=0xd6 | 0xd7..=0xdf => offset += 3,
877
878            // dddddddd: seek to offset dddddddd in the YM2612 PCM data bank
879            0xe0 => {
880                let pos = read_u32(buffer, &mut offset);
881                if let Some(chip) = find_chip(chips, ChipType::Ym2612, 0) {
882                    chip.seek_pcm(pos);
883                }
884            }
885            // ignored, consume four bytes
886            0xe1..=0xff => offset += 4,
887
888            // unrecognized command: no parameter bytes to skip
889            _ => {}
890        }
891
892        for _ in 0..delay {
893            let mut outputs = [0i32; 2];
894            for chip in chips.iter_mut() {
895                chip.generate(output_pos, output_step, &mut outputs);
896            }
897            output_pos += output_step;
898            wav_buffer.push(outputs[0]);
899            wav_buffer.push(outputs[1]);
900        }
901    }
902
903    wav_buffer
904}
905
906/// State for the VGM interface handler, which stores separate data banks for
907/// each access class (Io, AdpcmA, AdpcmB, Pcm).
908struct VgmHandlerState {
909    // Separate data banks for Io, AdpcmA, AdpcmB, and Pcm access classes.
910    data: [Vec<u8>; 4],
911}
912
913/// Return the index into `VgmHandlerState.data` for the given access class.
914fn data_index(access: AccessClass) -> usize {
915    match access {
916        AccessClass::Io => 0,
917        AccessClass::AdpcmA => 1,
918        AccessClass::AdpcmB => 2,
919        AccessClass::Pcm => 3,
920        _ => 0,
921    }
922}
923
924/// Write a byte to the appropriate data bank in `VgmHandlerState`, resizing
925/// the bank if necessary.
926fn write_byte(state: &mut VgmHandlerState, access: AccessClass, offset: u32, value: u8) {
927    let buffer = &mut state.data[data_index(access)];
928    let index = offset as usize;
929    if buffer.len() <= index {
930        buffer.resize(index + 1, 0);
931    }
932    buffer[index] = value;
933}
934
935fn read_byte(state: &VgmHandlerState, access: AccessClass, offset: u32) -> u8 {
936    state.data[data_index(access)]
937        .get(offset as usize)
938        .copied()
939        .unwrap_or(0)
940}
941
942/// Create an `InterfaceHandler` that writes to a `VgmHandlerState`, matching
943/// vgmrender.cpp's `vgm_handler`.
944fn vgm_handler_with_state(state: Rc<RefCell<VgmHandlerState>>) -> InterfaceHandler {
945    InterfaceHandler {
946        write_data: Some(Box::new({
947            let state = Rc::clone(&state);
948            move |access, base, data| {
949                let mut state = state.borrow_mut();
950                for (index, value) in data.iter().copied().enumerate() {
951                    write_byte(&mut state, access, base + index as u32, value);
952                }
953            }
954        })),
955        read_data: Some(Box::new({
956            let state = Rc::clone(&state);
957            move |access, base, length| {
958                let state = state.borrow();
959                (0..length)
960                    .map(|index| read_byte(&state, access, base + index))
961                    .collect()
962            }
963        })),
964        ymfm_external_read: Some(Box::new({
965            let state = Rc::clone(&state);
966            move |access, offset| read_byte(&state.borrow(), access, offset)
967        })),
968        ..Default::default()
969    }
970}
971
972/// Write a 16-bit stereo WAV file from interleaved (L, R) i32 samples,
973/// matching vgmrender.cpp's `write_wav` (samples are normalized so the
974/// loudest one hits roughly 80% of full scale).
975fn write_wav(path: &str, output_rate: u32, wav_buffer: &[i32]) -> io::Result<()> {
976    let max_scale = wav_buffer
977        .iter()
978        .map(|v| v.unsigned_abs())
979        .max()
980        .unwrap_or(0);
981    let max_scale = if max_scale == 0 {
982        eprintln!("The WAV file data will only contain silence.");
983        1
984    } else {
985        max_scale
986    };
987
988    let samples: Vec<i16> = wav_buffer
989        .iter()
990        .map(|&v| (i64::from(v) * 26000 / i64::from(max_scale)) as i16)
991        .collect();
992
993    let output: Box<dyn Write> = if path == "-" {
994        Box::new(io::stdout())
995    } else {
996        Box::new(fs::File::create(Path::new(path))?)
997    };
998    let mut out = BufWriter::new(output);
999    let data_len = (samples.len() * 2) as u32;
1000    let total_size = 40u32 + data_len;
1001    let byte_rate = output_rate * 2 * 2;
1002
1003    out.write_all(b"RIFF")?;
1004    out.write_all(&total_size.to_le_bytes())?;
1005    out.write_all(b"WAVE")?;
1006    out.write_all(b"fmt ")?;
1007    out.write_all(&16u32.to_le_bytes())?; // fmt chunk length
1008    out.write_all(&1u16.to_le_bytes())?; // PCM
1009    out.write_all(&2u16.to_le_bytes())?; // channels
1010    out.write_all(&output_rate.to_le_bytes())?;
1011    out.write_all(&byte_rate.to_le_bytes())?;
1012    out.write_all(&4u16.to_le_bytes())?; // block align
1013    out.write_all(&16u16.to_le_bytes())?; // bits/sample
1014    out.write_all(b"data")?;
1015    out.write_all(&data_len.to_le_bytes())?;
1016    for sample in &samples {
1017        out.write_all(&sample.to_le_bytes())?;
1018    }
1019    out.flush()
1020}
1021
1022/// Print usage information to stderr.
1023fn print_usage() {
1024    eprintln!("Usage: vgmrender <inputfile|-> -o <outputfile> [-r <rate>]");
1025    eprintln!("       Use '-' as <inputfile> to read VGM data from stdin.");
1026    eprintln!("       Use '-' as <outputfile> to write WAV data to stdout.");
1027}
1028
1029fn main() -> ExitCode {
1030    let args: Vec<String> = env::args().collect();
1031
1032    let mut input_file = None;
1033    let mut output_file = None;
1034    let mut output_rate: u32 = 44100;
1035    let mut arg_error = false;
1036
1037    let mut i = 1;
1038    while i < args.len() {
1039        let arg = args[i].as_str();
1040        match arg {
1041            "-o" | "--output" => {
1042                i += 1;
1043                output_file = args.get(i).cloned();
1044            }
1045            "-r" | "--samplerate" => {
1046                i += 1;
1047                output_rate = args.get(i).and_then(|s| s.parse().ok()).unwrap_or(44100);
1048            }
1049            "-" => input_file = Some(arg.to_string()),
1050            _ if arg.starts_with('-') => {
1051                eprintln!("Unknown argument: {arg}");
1052                arg_error = true;
1053            }
1054            _ => input_file = Some(arg.to_string()),
1055        }
1056        i += 1;
1057    }
1058
1059    let (Some(input_file), Some(output_file)) = (input_file, output_file) else {
1060        print_usage();
1061        return ExitCode::from(1);
1062    };
1063    if arg_error {
1064        print_usage();
1065        return ExitCode::from(1);
1066    }
1067
1068    let buffer = if input_file == "-" {
1069        let mut stdin = io::stdin();
1070        let mut buffer = Vec::new();
1071        match stdin.read_to_end(&mut buffer) {
1072            Ok(_) => buffer,
1073            Err(err) => {
1074                eprintln!("Error reading VGM data from stdin: {err}");
1075                return ExitCode::from(2);
1076            }
1077        }
1078    } else {
1079        match fs::read(&input_file) {
1080            Ok(buffer) => buffer,
1081            Err(err) => {
1082                eprintln!("Error opening file '{input_file}': {err}");
1083                return ExitCode::from(2);
1084            }
1085        }
1086    };
1087
1088    if buffer.len() < 64 || &buffer[0..4] != b"Vgm " {
1089        eprintln!("File '{input_file}' does not appear to be a valid VGM file");
1090        return ExitCode::from(4);
1091    }
1092
1093    let (data_start, mut chips) = parse_header(&buffer);
1094
1095    if chips.is_empty() {
1096        eprintln!("No compatible chips found, exiting.");
1097        return ExitCode::from(5);
1098    }
1099
1100    let wav_buffer = generate_all(&buffer, data_start, output_rate, &mut chips);
1101
1102    if let Err(err) = write_wav(&output_file, output_rate, &wav_buffer) {
1103        eprintln!("Error writing output file '{output_file}': {err}");
1104        return ExitCode::from(6);
1105    }
1106
1107    ExitCode::SUCCESS
1108}