mos_hardware/
sid.rs

1// copyright 2022 mikael lund aka wombat
2//
3// licensed under the apache license, version 2.0 (the "license");
4// you may not use this file except in compliance with the license.
5// you may obtain a copy of the license at
6//
7//     http://www.apache.org/licenses/license-2.0
8//
9// unless required by applicable law or agreed to in writing, software
10// distributed under the license is distributed on an "as is" basis,
11// without warranties or conditions of any kind, either express or implied.
12// see the license for the specific language governing permissions and
13// limitations under the license.
14
15//! Registers for the MOS Technology 6581/8580 SID (Sound Interface Device)
16//!
17//! SID is the built-in programmable sound generator chip of Commodore's CBM-II,
18//! Commodore 64, Commodore 128 and Commodore MAX Machine home computers.
19//! It was one of the first sound chips of its kind to be included in a home computer.
20
21use crate::*;
22use bitflags::bitflags;
23use core::mem::size_of;
24use rand_core::{Error, RngCore};
25use static_assertions::const_assert;
26use volatile_register::{RO, WO};
27
28bitflags! {
29    /// Control flags for the `Voice::control` register
30    pub struct VoiceControlFlags: u8 {
31        const GATE     = 0b0000_0001; // bit 0
32        /// Synch fundamental frequency of oscillator with fundamental frequency of neighboring voice
33        const SYNC     = 0b0000_0010; // bit 1
34        /// Set to replace triangle waveform w. ring modulation from neighbor voices
35        const RING_MODULATION = 0b0000_0100; // bit 2
36        /// Set to disable oscillations
37        const TEST     = 0b0000_1000; // bit 3
38        const TRIANGLE = 0b0001_0000; // bit 4
39        const SAWTOOTH = 0b0010_0000; // bit 5
40        const PULSE    = 0b0100_0000; // bit 6
41        const NOISE    = 0b1000_0000; // bit 7
42    }
43}
44
45#[repr(C, packed)]
46/// Registers for a single SID voice/channel
47pub struct Voice {
48    /// `FRELO`/`FRELO` Frequency control (0x00-0x01)
49    pub frequency: WO<u16>,
50    /// `PWLO`/`PWHI` Pulse waveform width (0x02-0x03)
51    pub pulse_width: WO<u16>,
52    /// `VCREG` Control register (0x04)
53    pub control: WO<VoiceControlFlags>,
54    /// `ATDCY` Attack/decay cycle duration (0x05)
55    pub attack_decay: WO<u8>,
56    /// `SUREL` Sustain/Release Control (0x06)
57    pub sustain_release: WO<u8>,
58}
59
60/// Attack times for `ATDCY`, bits 4-7 (milliseconds, `Ms`)
61pub enum AttackTime {
62    Ms2 = 0,
63    Ms8 = 1,
64    Ms16 = 2,
65    Ms24 = 3,
66    Ms38 = 4,
67    Ms56 = 5,
68    Ms68 = 6,
69    Ms80 = 7,
70    Ms100 = 8,
71    Ms250 = 9,
72    Ms500 = 10,
73    Ms800 = 11,
74    Ms1000 = 12,
75    Ms3000 = 13,
76    Ms5000 = 14,
77    Ms8000 = 15,
78}
79
80/// Sustain times for `ATDCY`, bits 0-3 (milliseconds, `Ms`)
81pub enum DecayTime {
82    Ms6 = 0,
83    Ms24 = 1,
84    Ms48 = 2,
85    Ms72 = 3,
86    Ms114 = 4,
87    Ms168 = 5,
88    Ms204 = 6,
89    Ms240 = 7,
90    Ms300 = 8,
91    Ms750 = 9,
92    Ms1500 = 10,
93    Ms2400 = 11,
94    Ms3000 = 12,
95    Ms9000 = 13,
96    Ms15000 = 14,
97    Ms24000 = 15,
98}
99
100/// Combines attack and decay times for register `ATDCY`
101///
102/// ## Example:
103/// ~~~
104/// const TIME: u8 = combine_attack_decay(AttackTime::Ms38, DecayTime::Ms240);
105/// ~~~
106pub const fn combine_attack_decay(attack_time: AttackTime, decay_time: DecayTime) -> u8 {
107    (attack_time as u8 * 16) + (decay_time as u8)
108}
109
110impl Voice {
111    /// Sets the attack/decay cycle duration (`ATDCY`)
112    ///
113    /// See e.g. Mapping the C64, page 162.
114    ///
115    /// ## Example:
116    /// ~~~
117    /// (*c64::SID).channel1.set_attack_decay(AttackTime::Ms38, DecayTime::Ms240);
118    /// ~~~
119    pub fn set_attack_decay(&self, attack_time: AttackTime, decay_time: DecayTime) {
120        let value = combine_attack_decay(attack_time, decay_time);
121        unsafe {
122            self.attack_decay.write(value);
123        }
124    }
125}
126
127const_assert!(size_of::<Voice>() == 7);
128
129#[repr(C, packed)]
130/// MOS Technology Sound Interface Device (SID)
131pub struct MOSSoundInterfaceDevice {
132    pub channel1: Voice,
133    pub channel2: Voice,
134    pub channel3: Voice,
135    pub filter_cutoff: WO<u16>, // 0x15
136    /// `RESON` Filter resonance control (0x17)
137    pub resonance_and_filter_setup: WO<u8>,
138    /// `SIGVOL` Volume and filter select (0x18)
139    pub volume_filter_mode: WO<u8>,
140    pub potentiometer_x: RO<u8>,     // 0x19
141    pub potentiometer_y: RO<u8>,     // 0x1a
142    pub channel3_oscillator: RO<u8>, // 0x1b
143    pub channel3_envelope: RO<u8>,   // 0x1c
144}
145
146const_assert!(size_of::<MOSSoundInterfaceDevice>() == 0x1d);
147
148impl MOSSoundInterfaceDevice {
149    /// Start noise generation on SID channel 3.
150    ///
151    /// Example:
152    /// ```
153    /// c64::sid().start_random_generator();
154    /// let random_byte = c64::sid().rand8(20);
155    /// ```
156    /// More information [here](https://www.atarimagazines.com/compute/issue72/random_numbers.php).
157    pub fn start_random_generator(&self) {
158        unsafe {
159            self.channel3.frequency.write(u16::MAX);
160            self.channel3.control.write(VoiceControlFlags::NOISE);
161        }
162    }
163
164    /// Random byte in the interval (0, max_value)
165    pub fn rand8(&self, max_value: u8) -> u8 {
166        loop {
167            let r = self.channel3_oscillator.read();
168            if r <= max_value {
169                return r;
170            }
171        }
172    }
173
174    /// Random byte in the interval (0, 255)
175    ///
176    /// # Examples
177    /// ~~~
178    /// c64::sid().start_random_generator();
179    /// let value = c64::sid().random_byte();
180    /// ~~~
181    /// More information [here](https://www.atarimagazines.com/compute/issue72/random_numbers.php).
182    /// Currently there's no way to select the subsong as this requires that the
183    /// accumulator is set. Possibly this can be done wrapping function pointers to raw
184    /// assembler code.
185    pub fn random_byte(&self) -> u8 {
186        self.channel3_oscillator.read()
187    }
188
189    /// Random word in the interval (0, max_value)
190    pub fn rand16(&self, max_value: u16) -> u16 {
191        loop {
192            let r = ((self.channel3_oscillator.read() as u16) << 8)
193                | (self.channel3_oscillator.read() as u16);
194            if r <= max_value {
195                return r;
196            }
197        }
198    }
199}
200
201/// Random number generator using the SID oscillator
202///
203/// Implements the [`rand::RngCore`](https://docs.rs/rand/latest/rand/trait.RngCore.html)
204/// trait and can thus be used with Rusts `rand` crate.
205/// For single random bytes, it is likely more efficient to use `random_byte()`
206/// from the SID chip directly, as the smallest integer implemented in `RngCore` is `u32`,
207/// i.e. four random bytes.
208///
209/// ## Examples
210/// ~~~
211/// use mos_hardware::{c64, sid};
212/// use rand::seq::SliceRandom;
213/// let mut rng = sid::SIDRng::new(c64::sid());
214/// let value = [11, 23].choose(&mut rng).unwrap(); // 11 or 23
215/// ~~~
216#[derive(Clone)]
217pub struct SIDRng {
218    sid: &'static MOSSoundInterfaceDevice,
219}
220
221impl SIDRng {
222    /// Initialize and start SID oscillator
223    pub fn new(sid_address: &'static MOSSoundInterfaceDevice) -> Self {
224        sid_address.start_random_generator();
225        Self { sid: sid_address }
226    }
227}
228
229impl RngCore for SIDRng {
230    fn next_u32(&mut self) -> u32 {
231        u32::from_ne_bytes([
232            self.sid.random_byte(),
233            self.sid.random_byte(),
234            self.sid.random_byte(),
235            self.sid.random_byte(),
236        ])
237    }
238
239    fn next_u64(&mut self) -> u64 {
240        u64::from_ne_bytes([
241            self.sid.random_byte(),
242            self.sid.random_byte(),
243            self.sid.random_byte(),
244            self.sid.random_byte(),
245            self.sid.random_byte(),
246            self.sid.random_byte(),
247            self.sid.random_byte(),
248            self.sid.random_byte(),
249        ])
250    }
251
252    fn fill_bytes(&mut self, dest: &mut [u8]) {
253        dest.iter_mut()
254            .for_each(|byte| *byte = self.sid.random_byte());
255    }
256
257    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
258        self.fill_bytes(dest);
259        Ok(())
260    }
261}
262
263/// Trait for loading and parsing a PSID file at compile time
264///
265/// The PSID file format is described
266/// [here](https://gist.github.com/cbmeeks/2b107f0a8d36fc461ebb056e94b2f4d6).
267/// Since arrays in rust cannot be larger than `isize`, songs larger than
268/// 32 kb cannot be loaded.
269///
270/// # Examples
271/// ~~~
272/// use mos_hardware::sid::SidTune;
273/// struct Music;
274/// impl SidTune for Music {
275///     const BYTES: &'static [u8] = core::include_bytes!("last_hero.sid");
276/// }
277/// let music = Music;
278/// unsafe  {
279///     music.to_memory(); // copy data to found load address (danger!)
280/// }
281/// music.init(0);     // call song initialisation routine
282/// music.play();      // call this at every frame
283/// ~~~
284pub trait SidTune {
285    /// Full SID file as const byte array. Typically you would set
286    /// this with `core::include_bytes!`.
287    const BYTES: &'static [u8];
288
289    /// True if data has an optional 2-byte header stating the load address (C64 style)
290    const HAS_BASIC_LOAD_ADDRESS: bool = matches!(
291        u16::from_be_bytes([Self::BYTES[0x08], Self::BYTES[0x09]]),
292        0
293    );
294
295    /// Offset where data begins, excluding any optional 2-byte load address
296    const DATA_OFFSET: usize = match Self::HAS_BASIC_LOAD_ADDRESS {
297        true => u16::from_be_bytes([Self::BYTES[0x06], Self::BYTES[0x07]]) as usize + 2,
298        false => u16::from_be_bytes([Self::BYTES[0x06], Self::BYTES[0x07]]) as usize,
299    };
300
301    /// Length of data part (exludes the optional 2-byte load address)
302    const DATA_LEN: usize = Self::BYTES.len() - Self::DATA_OFFSET;
303
304    /// Address of init routine
305    const INIT_ADDRESS: u16 = u16::from_be_bytes([Self::BYTES[0x0a], Self::BYTES[0x0b]]);
306
307    /// Function pointer to init routine
308    const INIT_PTR: *const unsafe extern "C" fn() -> () =
309        &Self::INIT_ADDRESS as *const u16 as *const unsafe extern "C" fn() -> ();
310
311    /// Address of play routine
312    const PLAY_ADDRESS: u16 = u16::from_be_bytes([Self::BYTES[0x0c], Self::BYTES[0x0d]]);
313
314    /// Function pointer to play routine
315    const PLAY_PTR: *const unsafe extern "C" fn() -> () =
316        &Self::PLAY_ADDRESS as *const u16 as *const unsafe extern "C" fn() -> ();
317
318    /// Number of subsongs
319    const NUM_SONGS: usize = u16::from_be_bytes([Self::BYTES[0x0e], Self::BYTES[0x0f]]) as usize;
320
321    /// Load address found either in PSID header or in data part
322    const LOAD_ADDRESS: u16 = match Self::HAS_BASIC_LOAD_ADDRESS {
323        true => u16::from_le_bytes([
324            Self::BYTES[Self::DATA_OFFSET - 2],
325            Self::BYTES[Self::DATA_OFFSET - 1],
326        ]),
327        false => u16::from_be_bytes([Self::BYTES[0x08], Self::BYTES[0x09]]),
328    };
329
330    fn num_songs(&self) -> usize {
331        Self::NUM_SONGS
332    }
333
334    /// Call song initialisation routine
335    ///
336    /// Before calling the init routine found in the the PSID file, the
337    /// accumulator (A) is set to the `song` number. This is done by placing
338    /// 6502 wrapper code at the end of the SID file.
339    ///
340    /// ## Todo
341    ///
342    /// It would be nice to let the compiler decide where to place the
343    /// wrapper code (`address`), but so far no luck.
344    fn init(&self, song: u8) {
345        let [high, low] = Self::INIT_ADDRESS.to_be_bytes();
346        let address = Self::LOAD_ADDRESS as usize + Self::DATA_LEN;
347        let init_fn = &address as *const usize as *const unsafe extern "C" fn() -> ();
348        unsafe {
349            // 0xa9 = lda; 0x4c = jmp
350            *(address as *mut [u8; 5]) = [0xa9, song, 0x4c, low, high];
351            (*init_fn)();
352        }
353    }
354
355    /// Call song play routine
356    fn play(&self) {
357        unsafe { (*(Self::PLAY_PTR))() }
358    }
359
360    /// Copies data into memory at load address specified in PSID file.
361    ///
362    /// # Safety
363    /// Unsafe, as this will perform copy into hard-coded
364    /// memory pool that may clash with stack or allocated heap memory.
365    unsafe fn to_memory(&self)
366    where
367        [(); Self::DATA_LEN]:,
368    {
369        let dst = Self::LOAD_ADDRESS as *mut [u8; Self::DATA_LEN];
370        *dst = Self::BYTES[Self::DATA_OFFSET..Self::BYTES.len()]
371            .try_into()
372            .unwrap();
373    }
374}