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
#![doc = include_str!("../README.md")]
use cxx::UniquePtr;
mod callback;
pub use callback::{InterfaceCallbacks, InterfaceHandler};
pub(crate) use callback::{
advance_clock, default_callbacks, read_data, write_data, ymfm_external_read,
ymfm_external_write, ymfm_is_busy, ymfm_set_busy_end, ymfm_set_timer, ymfm_update_irq,
};
/// Convenience alias for the common case of holding a chip instance.
pub type ChipPtr = UniquePtr<ffi::Chip>;
#[cxx::bridge(namespace = "ymfm_sys")]
pub mod ffi {
extern "Rust" {
type InterfaceCallbacks;
fn default_callbacks() -> Box<InterfaceCallbacks>;
fn advance_clock(callbacks: &InterfaceCallbacks, clocks: i64) -> u8;
fn read_data(
callbacks: &InterfaceCallbacks,
access: AccessClass,
base: u32,
length: u32,
) -> Vec<u8>;
fn write_data(callbacks: &InterfaceCallbacks, access: AccessClass, base: u32, data: &[u8]);
fn ymfm_external_read(
callbacks: &InterfaceCallbacks,
access: AccessClass,
offset: u32,
) -> u8;
fn ymfm_external_write(
callbacks: &InterfaceCallbacks,
access: AccessClass,
offset: u32,
data: u8,
);
fn ymfm_is_busy(callbacks: &InterfaceCallbacks) -> bool;
fn ymfm_set_busy_end(callbacks: &InterfaceCallbacks, clocks: u32);
fn ymfm_set_timer(callbacks: &InterfaceCallbacks, tnum: u32, duration_in_clocks: i32);
fn ymfm_update_irq(callbacks: &InterfaceCallbacks, asserted: bool);
}
/// Supported Yamaha FM/SSG chip families.
///
/// `Ym2610B` exists only to select the YM2610B variant at creation time;
/// `Chip::chip_type` normalizes it back to `Ym2610`, matching how ymfm
/// itself treats the two revisions identically for register routing.
#[repr(u32)]
enum ChipType {
Ym2149,
Ym2151,
Ym2164,
Ym2203,
Ym2413,
Ym2423,
Ym2608,
Ym2610,
Ym2610B,
Ym2612,
Ym3438,
Ymf276,
Ym3526,
Ym3533,
Y8950,
Ym3812,
Ymf262,
Ymf281,
Ymf289B,
Ymf278B,
Ymf288,
Ym3806,
Ds1001,
Ym2414,
}
/// External data classes a chip may read ROM/RAM data from.
#[repr(u32)]
enum AccessClass {
Io,
AdpcmA,
AdpcmB,
Pcm,
}
/// Sample-rate/accuracy tradeoff, via the ymfm `opn_fidelity` setting.
/// Only meaningful for YM2203/YM2608/YM2610/YM2610B; a no-op elsewhere.
#[repr(u32)]
enum Fidelity {
Max,
Min,
Med,
}
unsafe extern "C++" {
include!("ymfm-sys/src/shim.h");
/// Opaque handle to a single emulated chip instance.
type Chip;
/// Create a chip with all optional interface callbacks disabled.
fn create_chip(chip_type: ChipType, clock: u32) -> UniquePtr<Chip>;
/// Create a chip and forward ymfm interface callbacks to `callbacks`.
fn create_chip_with_callbacks(
chip_type: ChipType,
clock: u32,
callbacks: Box<InterfaceCallbacks>,
) -> UniquePtr<Chip>;
/// Which chip this instance represents.
fn chip_type(self: &Chip) -> ChipType;
/// Number of output channels this chip produces per generated sample
/// (via the concrete ymfm chip class's `OUTPUTS` constant).
fn channels(self: &Chip) -> u32;
/// Native output sample rate for the clock this chip was created
/// with (via the ymfm `sample_rate(uint32_t input_clock)` API).
fn sample_rate(self: &Chip) -> u32;
/// Reset the chip to its post-power-on state (via the ymfm `reset()` API).
fn reset(self: Pin<&mut Chip>);
/// Select the sample-rate/accuracy tradeoff (via the ymfm
/// `set_fidelity(opn_fidelity)`). Only meaningful for
/// YM2203/YM2608/YM2610/YM2610B; a no-op on other chips.
fn set_fidelity(self: Pin<&mut Chip>, fidelity: Fidelity);
/// Replace the 0x90-byte instrument data on OPLL-family chips.
/// Returns false for unsupported chip types or an incorrectly sized
/// data buffer.
fn set_instrument_data(self: Pin<&mut Chip>, data: &[u8]) -> bool;
/// Write to a register or chip port at `offset`, via the upstream
/// `write(offset, data)` API. The common mapping is 0/1 for the
/// address/data ports. OPN/OPNA chips generally use 2/3 for their
/// extended address/data ports; YMF262/YMF289B use 2 for the upper
/// address and 3 for regular data; YMF278B uses 4/5 for PCM
/// address/data. YM2149 uses 2 for write data. Unsupported offsets
/// follow the selected chip's upstream behavior.
fn write(self: Pin<&mut Chip>, offset: u32, data: u8);
/// Read from a chip port at `offset`, via the upstream `read(offset)`
/// API. The common mapping is 0 for status and 1 for data; extended
/// status/data and chip-specific ports use the offsets defined by the
/// selected upstream chip. YM2149 reads its data port at offset 3.
fn read(self: Pin<&mut Chip>, offset: u32) -> u8;
/// Generate `buffer.len() / channels()` samples at the chip's native
/// sample rate, overwriting `buffer` (channel-interleaved); wraps
/// the ymfm `generate(output_data*, numsamples)` API. This generates
/// one native sample at a time. For each sample, it also advances the
/// internal clock counter used by
/// timers, including those required by modes such as CSM, and by
/// BUSY state tracking.
fn generate(self: Pin<&mut Chip>, buffer: &mut [i32]);
/// Serialize the full internal chip state via the ymfm
/// `save_restore(ymfm_saved_state&)` with `saving = true`).
fn save_state(self: Pin<&mut Chip>) -> Vec<u8>;
/// Restore state previously produced by `save_state` (via the ymfm
/// `save_restore(ymfm_saved_state&)` with `saving = false`). The
/// chip must be of the same type and clock as when the state was
/// saved; ymfm does not version or validate the saved data itself.
fn restore_state(self: Pin<&mut Chip>, data: &[u8]);
}
}