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