mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-24 08:41:34 +11:00
Merge pull request #60 from gwilymk/actual-sound-mixing
Actual sound mixing
This commit is contained in:
commit
fd3fe32292
|
@ -11,25 +11,29 @@ pub fn main() -> ! {
|
||||||
|
|
||||||
gba.sound.enable();
|
gba.sound.enable();
|
||||||
|
|
||||||
let sweep_settings = sound::SweepSettings::default();
|
let sweep_settings = sound::dmg::SweepSettings::default();
|
||||||
gba.sound.channel1().play_sound(
|
gba.sound.channel1().play_sound(
|
||||||
1024,
|
1024,
|
||||||
Some(0),
|
Some(0),
|
||||||
&sweep_settings,
|
&sweep_settings,
|
||||||
&sound::EnvelopeSettings::default(),
|
&sound::dmg::EnvelopeSettings::default(),
|
||||||
sound::DutyCycle::Half,
|
sound::dmg::DutyCycle::Half,
|
||||||
);
|
);
|
||||||
|
|
||||||
gba.sound.channel2().play_sound(
|
gba.sound.channel2().play_sound(
|
||||||
1524,
|
1524,
|
||||||
Some(0),
|
Some(0),
|
||||||
&sound::EnvelopeSettings::default(),
|
&sound::dmg::EnvelopeSettings::default(),
|
||||||
sound::DutyCycle::Half,
|
sound::dmg::DutyCycle::Half,
|
||||||
);
|
);
|
||||||
|
|
||||||
gba.sound
|
gba.sound.noise().play_sound(
|
||||||
.noise()
|
Some(0),
|
||||||
.play_sound(Some(0), &sound::EnvelopeSettings::default(), 4, false, 1);
|
&sound::dmg::EnvelopeSettings::default(),
|
||||||
|
4,
|
||||||
|
false,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
|
|
BIN
agb/examples/i-will-not-let-you-let-me-down.raw
Normal file
BIN
agb/examples/i-will-not-let-you-let-me-down.raw
Normal file
Binary file not shown.
27
agb/examples/mixer_basic.rs
Normal file
27
agb/examples/mixer_basic.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
extern crate agb;
|
||||||
|
|
||||||
|
use agb::sound::mixer::SoundChannel;
|
||||||
|
use agb::Gba;
|
||||||
|
|
||||||
|
// Music - "I will not let you let me down" by Josh Woodward, free download at http://joshwoodward.com
|
||||||
|
const I_WILL_NOT_LET_YOU_LET_ME_DOWN: &[u8] = include_bytes!("i-will-not-let-you-let-me-down.raw");
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn main() -> ! {
|
||||||
|
let mut gba = Gba::new();
|
||||||
|
let vblank_provider = gba.display.vblank.get();
|
||||||
|
|
||||||
|
let mut mixer = gba.mixer.mixer();
|
||||||
|
mixer.enable();
|
||||||
|
|
||||||
|
let channel = SoundChannel::new(I_WILL_NOT_LET_YOU_LET_ME_DOWN);
|
||||||
|
mixer.play_sound(channel);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
vblank_provider.wait_for_VBlank();
|
||||||
|
mixer.vblank();
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,7 +50,8 @@ static mut GBASINGLE: single::Singleton<Gba> = single::Singleton::new(unsafe { G
|
||||||
|
|
||||||
pub struct Gba {
|
pub struct Gba {
|
||||||
pub display: display::Display,
|
pub display: display::Display,
|
||||||
pub sound: sound::Sound,
|
pub sound: sound::dmg::Sound,
|
||||||
|
pub mixer: sound::mixer::MixerController,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gba {
|
impl Gba {
|
||||||
|
@ -61,7 +62,8 @@ impl Gba {
|
||||||
const unsafe fn single_new() -> Self {
|
const unsafe fn single_new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
display: display::Display::new(),
|
display: display::Display::new(),
|
||||||
sound: sound::Sound::new(),
|
sound: sound::dmg::Sound::new(),
|
||||||
|
mixer: sound::mixer::MixerController::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
238
agb/src/sound/dmg.rs
Normal file
238
agb/src/sound/dmg.rs
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
use crate::memory_mapped::MemoryMapped;
|
||||||
|
|
||||||
|
const CHANNEL_1_SWEEP: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0060) };
|
||||||
|
const CHANNEL_1_LENGTH_DUTY_ENVELOPE: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0062) };
|
||||||
|
const CHANNEL_1_FREQUENCY_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0064) };
|
||||||
|
|
||||||
|
const CHANNEL_2_LENGTH_DUTY_ENVELOPE: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0068) };
|
||||||
|
const CHANNEL_2_FREQUENCY_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_006c) };
|
||||||
|
|
||||||
|
const CHANNEL_4_LENGTH_ENVELOPE: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0078) };
|
||||||
|
const CHANNEL_4_FREQUENCY_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_007c) };
|
||||||
|
|
||||||
|
const MASTER_SOUND_VOLUME_ENABLE: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0080) };
|
||||||
|
const MASTER_SOUND_VOLUME_MIXING: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0082) };
|
||||||
|
const MASTER_SOUND_STATUS: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0084) };
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Sound {}
|
||||||
|
|
||||||
|
impl Sound {
|
||||||
|
pub(crate) const unsafe fn new() -> Self {
|
||||||
|
Sound {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn channel1(&self) -> Channel1 {
|
||||||
|
Channel1 {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn channel2(&self) -> Channel2 {
|
||||||
|
Channel2 {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn noise(&self) -> Noise {
|
||||||
|
Noise {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable(&self) {
|
||||||
|
MASTER_SOUND_STATUS.set_bits(1, 1, 7);
|
||||||
|
|
||||||
|
#[allow(clippy::unusual_byte_groupings)] // I've split these like this for a reason
|
||||||
|
MASTER_SOUND_VOLUME_ENABLE.set(0b1111_1111_0_111_0_111);
|
||||||
|
MASTER_SOUND_VOLUME_MIXING.set(0b10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Channel1 {}
|
||||||
|
|
||||||
|
impl Channel1 {
|
||||||
|
pub fn play_sound(
|
||||||
|
&self,
|
||||||
|
frequency: u16,
|
||||||
|
length: Option<u8>,
|
||||||
|
sweep_settings: &SweepSettings,
|
||||||
|
envelope_settings: &EnvelopeSettings,
|
||||||
|
duty_cycle: DutyCycle,
|
||||||
|
) {
|
||||||
|
CHANNEL_1_SWEEP.set(sweep_settings.as_bits());
|
||||||
|
let length_bits = length.unwrap_or(0) as u16;
|
||||||
|
assert!(length_bits < 64, "Length must be less than 64");
|
||||||
|
|
||||||
|
let length_flag: u16 = length.map(|_| 1 << 14).unwrap_or(0);
|
||||||
|
let initial: u16 = 1 << 15;
|
||||||
|
|
||||||
|
assert!(frequency < 2048, "Frequency must be less than 2048");
|
||||||
|
|
||||||
|
CHANNEL_1_LENGTH_DUTY_ENVELOPE
|
||||||
|
.set(envelope_settings.as_bits() | duty_cycle.as_bits() | length_bits);
|
||||||
|
CHANNEL_1_FREQUENCY_CONTROL.set(frequency | length_flag | initial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Channel2 {}
|
||||||
|
|
||||||
|
impl Channel2 {
|
||||||
|
pub fn play_sound(
|
||||||
|
&self,
|
||||||
|
frequency: u16,
|
||||||
|
length: Option<u8>,
|
||||||
|
envelope_settings: &EnvelopeSettings,
|
||||||
|
duty_cycle: DutyCycle,
|
||||||
|
) {
|
||||||
|
let length_bits = length.unwrap_or(0) as u16;
|
||||||
|
assert!(length_bits < 64, "Length must be less than 64");
|
||||||
|
|
||||||
|
let length_flag: u16 = length.map(|_| 1 << 14).unwrap_or(0);
|
||||||
|
let initial: u16 = 1 << 15;
|
||||||
|
|
||||||
|
assert!(frequency < 2048, "Frequency must be less than 2048");
|
||||||
|
|
||||||
|
CHANNEL_2_LENGTH_DUTY_ENVELOPE
|
||||||
|
.set(envelope_settings.as_bits() | duty_cycle.as_bits() | length_bits);
|
||||||
|
CHANNEL_2_FREQUENCY_CONTROL.set(frequency | length_flag | initial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Noise {}
|
||||||
|
|
||||||
|
impl Noise {
|
||||||
|
pub fn play_sound(
|
||||||
|
&self,
|
||||||
|
length: Option<u8>,
|
||||||
|
envelope_setting: &EnvelopeSettings,
|
||||||
|
frequency_divider: u8,
|
||||||
|
counter_step_width_15: bool,
|
||||||
|
shift_clock_frequency: u8,
|
||||||
|
) {
|
||||||
|
let length_bits = length.unwrap_or(0) as u16;
|
||||||
|
assert!(length_bits < 64, "length must be less than 16");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
frequency_divider < 8,
|
||||||
|
"frequency divider must be less than 8"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
shift_clock_frequency < 16,
|
||||||
|
"frequency clock divider must be less than 16"
|
||||||
|
);
|
||||||
|
|
||||||
|
let length_flag: u16 = length.map(|_| 1 << 14).unwrap_or(0);
|
||||||
|
let initial: u16 = 1 << 15;
|
||||||
|
|
||||||
|
let counter_step_bit = if counter_step_width_15 { 0 } else { 1 << 3 };
|
||||||
|
|
||||||
|
CHANNEL_4_LENGTH_ENVELOPE.set(length_bits | envelope_setting.as_bits());
|
||||||
|
CHANNEL_4_FREQUENCY_CONTROL.set(
|
||||||
|
(frequency_divider as u16)
|
||||||
|
| counter_step_bit
|
||||||
|
| ((shift_clock_frequency as u16) << 4)
|
||||||
|
| length_flag
|
||||||
|
| initial,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SoundDirection {
|
||||||
|
Increase,
|
||||||
|
Decrease,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SoundDirection {
|
||||||
|
fn as_bits(&self) -> u16 {
|
||||||
|
match &self {
|
||||||
|
SoundDirection::Increase => 1,
|
||||||
|
SoundDirection::Decrease => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SweepSettings {
|
||||||
|
number_of_sweep_shifts: u8,
|
||||||
|
sound_direction: SoundDirection,
|
||||||
|
sweep_time: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SweepSettings {
|
||||||
|
pub fn new(
|
||||||
|
number_of_sweep_shifts: u8,
|
||||||
|
sound_direction: SoundDirection,
|
||||||
|
sweep_time: u8,
|
||||||
|
) -> Self {
|
||||||
|
assert!(
|
||||||
|
number_of_sweep_shifts < 8,
|
||||||
|
"Number of sweep shifts must be less than 8"
|
||||||
|
);
|
||||||
|
assert!(sweep_time < 8, "Sweep time must be less than 8");
|
||||||
|
|
||||||
|
SweepSettings {
|
||||||
|
number_of_sweep_shifts,
|
||||||
|
sound_direction,
|
||||||
|
sweep_time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_bits(&self) -> u16 {
|
||||||
|
((self.number_of_sweep_shifts as u16) & 0b111)
|
||||||
|
| ((1 - self.sound_direction.as_bits()) << 3) // sweep works backwards
|
||||||
|
| ((self.sweep_time as u16) & 0b111) << 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SweepSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
SweepSettings::new(0, SoundDirection::Increase, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EnvelopeSettings {
|
||||||
|
step_time: u8,
|
||||||
|
direction: SoundDirection,
|
||||||
|
initial_volume: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnvelopeSettings {
|
||||||
|
pub fn new(step_time: u8, direction: SoundDirection, initial_volume: u8) -> Self {
|
||||||
|
assert!(step_time < 8, "Step time must be less than 8");
|
||||||
|
assert!(initial_volume < 16, "Initial volume must be less that 16");
|
||||||
|
EnvelopeSettings {
|
||||||
|
step_time,
|
||||||
|
direction,
|
||||||
|
initial_volume,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_bits(&self) -> u16 {
|
||||||
|
(self.step_time as u16) << 8
|
||||||
|
| (self.direction.as_bits() << 11)
|
||||||
|
| ((self.initial_volume as u16) << 12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EnvelopeSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
EnvelopeSettings::new(0, SoundDirection::Increase, 15)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DutyCycle {
|
||||||
|
OneEighth,
|
||||||
|
OneQuarter,
|
||||||
|
Half,
|
||||||
|
ThreeQuarters,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DutyCycle {
|
||||||
|
fn as_bits(&self) -> u16 {
|
||||||
|
use DutyCycle::*;
|
||||||
|
|
||||||
|
match &self {
|
||||||
|
OneEighth => 0,
|
||||||
|
OneQuarter => 1,
|
||||||
|
Half => 2,
|
||||||
|
ThreeQuarters => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
181
agb/src/sound/mixer.rs
Normal file
181
agb/src/sound/mixer.rs
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
use crate::memory_mapped::MemoryMapped;
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct MixerController {}
|
||||||
|
|
||||||
|
impl MixerController {
|
||||||
|
pub(crate) const fn new() -> Self {
|
||||||
|
MixerController {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mixer(&mut self) -> Mixer {
|
||||||
|
Mixer::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Mixer {
|
||||||
|
buffer: MixerBuffer,
|
||||||
|
channels: [Option<SoundChannel>; 16],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mixer {
|
||||||
|
fn new() -> Self {
|
||||||
|
Mixer {
|
||||||
|
buffer: MixerBuffer::new(),
|
||||||
|
channels: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable(&self) {
|
||||||
|
set_timer_counter_for_frequency_and_enable(SOUND_FREQUENCY);
|
||||||
|
set_sound_control_register_for_mixer();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vblank(&mut self) {
|
||||||
|
self.buffer.swap();
|
||||||
|
self.buffer.clear();
|
||||||
|
|
||||||
|
for channel in self.channels.iter_mut() {
|
||||||
|
let mut has_finished = false;
|
||||||
|
|
||||||
|
if let Some(some_channel) = channel {
|
||||||
|
self.buffer.write_channel(some_channel);
|
||||||
|
some_channel.pos += SOUND_BUFFER_SIZE;
|
||||||
|
|
||||||
|
if some_channel.pos >= some_channel.data.len() {
|
||||||
|
has_finished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_finished {
|
||||||
|
channel.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play_sound(&mut self, new_channel: SoundChannel) {
|
||||||
|
for channel in self.channels.iter_mut() {
|
||||||
|
if channel.is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.replace(new_channel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("Cannot play more than 16 sounds at once");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SoundChannel {
|
||||||
|
data: &'static [u8],
|
||||||
|
pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SoundChannel {
|
||||||
|
pub fn new(data: &'static [u8]) -> Self {
|
||||||
|
SoundChannel { data, pos: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// I've picked one frequency that works nicely. But there are others that work nicely
|
||||||
|
// which we may want to consider in the future: https://web.archive.org/web/20070608011909/http://deku.gbadev.org/program/sound1.html
|
||||||
|
const SOUND_FREQUENCY: i32 = 10512;
|
||||||
|
const SOUND_BUFFER_SIZE: usize = 176;
|
||||||
|
|
||||||
|
struct MixerBuffer {
|
||||||
|
buffer1: [i8; SOUND_BUFFER_SIZE],
|
||||||
|
buffer2: [i8; SOUND_BUFFER_SIZE],
|
||||||
|
|
||||||
|
buffer_1_active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MixerBuffer {
|
||||||
|
fn new() -> Self {
|
||||||
|
MixerBuffer {
|
||||||
|
buffer1: [0; SOUND_BUFFER_SIZE],
|
||||||
|
buffer2: [0; SOUND_BUFFER_SIZE],
|
||||||
|
|
||||||
|
buffer_1_active: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn swap(&mut self) {
|
||||||
|
self.buffer_1_active = !self.buffer_1_active;
|
||||||
|
|
||||||
|
if self.buffer_1_active {
|
||||||
|
enable_dma1_for_sound(&self.buffer1);
|
||||||
|
} else {
|
||||||
|
enable_dma1_for_sound(&self.buffer2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self) {
|
||||||
|
self.get_write_buffer().fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_channel(&mut self, channel: &SoundChannel) {
|
||||||
|
let data_to_copy = &channel.data[channel.pos..(channel.pos + SOUND_BUFFER_SIZE)];
|
||||||
|
let place_to_write_to = self.get_write_buffer();
|
||||||
|
|
||||||
|
for (i, v) in data_to_copy.iter().enumerate() {
|
||||||
|
let v = *v as i8;
|
||||||
|
place_to_write_to[i] = place_to_write_to[i].saturating_add(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_write_buffer(&mut self) -> &mut [i8; SOUND_BUFFER_SIZE] {
|
||||||
|
if self.buffer_1_active {
|
||||||
|
&mut self.buffer2
|
||||||
|
} else {
|
||||||
|
&mut self.buffer1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once we have proper DMA support, we should use that rather than hard coding these here too
|
||||||
|
const DMA1_SOURCE_ADDR: MemoryMapped<u32> = unsafe { MemoryMapped::new(0x0400_00bc) };
|
||||||
|
const DMA1_DEST_ADDR: MemoryMapped<u32> = unsafe { MemoryMapped::new(0x0400_00c0) };
|
||||||
|
const _DMA1_WORD_COUNT: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_00c4) }; // sound ignores this for some reason
|
||||||
|
const DMA1_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_00c6) };
|
||||||
|
|
||||||
|
const FIFOA_DEST_ADDR: u32 = 0x0400_00a0;
|
||||||
|
|
||||||
|
// Similarly for proper timer support
|
||||||
|
const TIMER0_COUNTER: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0100) };
|
||||||
|
const TIMER0_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0102) };
|
||||||
|
|
||||||
|
const SOUND_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0082) };
|
||||||
|
const SOUND_CONTROL_X: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0084) };
|
||||||
|
|
||||||
|
fn enable_dma1_for_sound(sound_memory: &[i8]) {
|
||||||
|
let dest_fixed: u16 = 2 << 5; // dest addr control = fixed
|
||||||
|
let repeat: u16 = 1 << 9;
|
||||||
|
let transfer_type: u16 = 1 << 10; // transfer in words
|
||||||
|
let dma_start_timing: u16 = 3 << 12; // sound fifo timing
|
||||||
|
let enable: u16 = 1 << 15; // enable
|
||||||
|
|
||||||
|
DMA1_CONTROL.set(0);
|
||||||
|
DMA1_SOURCE_ADDR.set(sound_memory.as_ptr() as u32);
|
||||||
|
DMA1_DEST_ADDR.set(FIFOA_DEST_ADDR);
|
||||||
|
DMA1_CONTROL.set(dest_fixed | repeat | transfer_type | dma_start_timing | enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_sound_control_register_for_mixer() {
|
||||||
|
let sound_a_volume_100: u16 = 1 << 2;
|
||||||
|
let sound_a_rout: u16 = 1 << 8;
|
||||||
|
let sound_a_lout: u16 = 1 << 9;
|
||||||
|
let sound_a_fifo_reset: u16 = 1 << 11;
|
||||||
|
|
||||||
|
SOUND_CONTROL.set(sound_a_volume_100 | sound_a_rout | sound_a_lout | sound_a_fifo_reset);
|
||||||
|
|
||||||
|
// master sound enable
|
||||||
|
SOUND_CONTROL_X.set(1 << 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_timer_counter_for_frequency_and_enable(frequency: i32) {
|
||||||
|
let counter = 65536 - (16777216 / frequency);
|
||||||
|
TIMER0_COUNTER.set(counter as u16);
|
||||||
|
|
||||||
|
TIMER0_CONTROL.set(1 << 7); // enable the timer
|
||||||
|
}
|
|
@ -1,238 +1,2 @@
|
||||||
use crate::memory_mapped::MemoryMapped;
|
pub mod dmg;
|
||||||
|
pub mod mixer;
|
||||||
const CHANNEL_1_SWEEP: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0060) };
|
|
||||||
const CHANNEL_1_LENGTH_DUTY_ENVELOPE: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0062) };
|
|
||||||
const CHANNEL_1_FREQUENCY_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0064) };
|
|
||||||
|
|
||||||
const CHANNEL_2_LENGTH_DUTY_ENVELOPE: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0068) };
|
|
||||||
const CHANNEL_2_FREQUENCY_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_006c) };
|
|
||||||
|
|
||||||
const CHANNEL_4_LENGTH_ENVELOPE: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0078) };
|
|
||||||
const CHANNEL_4_FREQUENCY_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_007c) };
|
|
||||||
|
|
||||||
const MASTER_SOUND_VOLUME_ENABLE: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0080) };
|
|
||||||
const MASTER_SOUND_VOLUME_MIXING: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0082) };
|
|
||||||
const MASTER_SOUND_STATUS: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0084) };
|
|
||||||
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct Sound {}
|
|
||||||
|
|
||||||
impl Sound {
|
|
||||||
pub(crate) const unsafe fn new() -> Self {
|
|
||||||
Sound {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn channel1(&self) -> Channel1 {
|
|
||||||
Channel1 {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn channel2(&self) -> Channel2 {
|
|
||||||
Channel2 {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn noise(&self) -> Noise {
|
|
||||||
Noise {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enable(&self) {
|
|
||||||
MASTER_SOUND_STATUS.set_bits(1, 1, 7);
|
|
||||||
|
|
||||||
#[allow(clippy::unusual_byte_groupings)] // I've split these like this for a reason
|
|
||||||
MASTER_SOUND_VOLUME_ENABLE.set(0b1111_1111_0_111_0_111);
|
|
||||||
MASTER_SOUND_VOLUME_MIXING.set(0b10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct Channel1 {}
|
|
||||||
|
|
||||||
impl Channel1 {
|
|
||||||
pub fn play_sound(
|
|
||||||
&self,
|
|
||||||
frequency: u16,
|
|
||||||
length: Option<u8>,
|
|
||||||
sweep_settings: &SweepSettings,
|
|
||||||
envelope_settings: &EnvelopeSettings,
|
|
||||||
duty_cycle: DutyCycle,
|
|
||||||
) {
|
|
||||||
CHANNEL_1_SWEEP.set(sweep_settings.as_bits());
|
|
||||||
let length_bits = length.unwrap_or(0) as u16;
|
|
||||||
assert!(length_bits < 64, "Length must be less than 64");
|
|
||||||
|
|
||||||
let length_flag: u16 = length.map(|_| 1 << 14).unwrap_or(0);
|
|
||||||
let initial: u16 = 1 << 15;
|
|
||||||
|
|
||||||
assert!(frequency < 2048, "Frequency must be less than 2048");
|
|
||||||
|
|
||||||
CHANNEL_1_LENGTH_DUTY_ENVELOPE
|
|
||||||
.set(envelope_settings.as_bits() | duty_cycle.as_bits() | length_bits);
|
|
||||||
CHANNEL_1_FREQUENCY_CONTROL.set(frequency | length_flag | initial);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct Channel2 {}
|
|
||||||
|
|
||||||
impl Channel2 {
|
|
||||||
pub fn play_sound(
|
|
||||||
&self,
|
|
||||||
frequency: u16,
|
|
||||||
length: Option<u8>,
|
|
||||||
envelope_settings: &EnvelopeSettings,
|
|
||||||
duty_cycle: DutyCycle,
|
|
||||||
) {
|
|
||||||
let length_bits = length.unwrap_or(0) as u16;
|
|
||||||
assert!(length_bits < 64, "Length must be less than 64");
|
|
||||||
|
|
||||||
let length_flag: u16 = length.map(|_| 1 << 14).unwrap_or(0);
|
|
||||||
let initial: u16 = 1 << 15;
|
|
||||||
|
|
||||||
assert!(frequency < 2048, "Frequency must be less than 2048");
|
|
||||||
|
|
||||||
CHANNEL_2_LENGTH_DUTY_ENVELOPE
|
|
||||||
.set(envelope_settings.as_bits() | duty_cycle.as_bits() | length_bits);
|
|
||||||
CHANNEL_2_FREQUENCY_CONTROL.set(frequency | length_flag | initial);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct Noise {}
|
|
||||||
|
|
||||||
impl Noise {
|
|
||||||
pub fn play_sound(
|
|
||||||
&self,
|
|
||||||
length: Option<u8>,
|
|
||||||
envelope_setting: &EnvelopeSettings,
|
|
||||||
frequency_divider: u8,
|
|
||||||
counter_step_width_15: bool,
|
|
||||||
shift_clock_frequency: u8,
|
|
||||||
) {
|
|
||||||
let length_bits = length.unwrap_or(0) as u16;
|
|
||||||
assert!(length_bits < 64, "length must be less than 16");
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
frequency_divider < 8,
|
|
||||||
"frequency divider must be less than 8"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
shift_clock_frequency < 16,
|
|
||||||
"frequency clock divider must be less than 16"
|
|
||||||
);
|
|
||||||
|
|
||||||
let length_flag: u16 = length.map(|_| 1 << 14).unwrap_or(0);
|
|
||||||
let initial: u16 = 1 << 15;
|
|
||||||
|
|
||||||
let counter_step_bit = if counter_step_width_15 { 0 } else { 1 << 3 };
|
|
||||||
|
|
||||||
CHANNEL_4_LENGTH_ENVELOPE.set(length_bits | envelope_setting.as_bits());
|
|
||||||
CHANNEL_4_FREQUENCY_CONTROL.set(
|
|
||||||
(frequency_divider as u16)
|
|
||||||
| counter_step_bit
|
|
||||||
| ((shift_clock_frequency as u16) << 4)
|
|
||||||
| length_flag
|
|
||||||
| initial,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum SoundDirection {
|
|
||||||
Increase,
|
|
||||||
Decrease,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SoundDirection {
|
|
||||||
fn as_bits(&self) -> u16 {
|
|
||||||
match &self {
|
|
||||||
SoundDirection::Increase => 1,
|
|
||||||
SoundDirection::Decrease => 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SweepSettings {
|
|
||||||
number_of_sweep_shifts: u8,
|
|
||||||
sound_direction: SoundDirection,
|
|
||||||
sweep_time: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SweepSettings {
|
|
||||||
pub fn new(
|
|
||||||
number_of_sweep_shifts: u8,
|
|
||||||
sound_direction: SoundDirection,
|
|
||||||
sweep_time: u8,
|
|
||||||
) -> Self {
|
|
||||||
assert!(
|
|
||||||
number_of_sweep_shifts < 8,
|
|
||||||
"Number of sweep shifts must be less than 8"
|
|
||||||
);
|
|
||||||
assert!(sweep_time < 8, "Sweep time must be less than 8");
|
|
||||||
|
|
||||||
SweepSettings {
|
|
||||||
number_of_sweep_shifts,
|
|
||||||
sound_direction,
|
|
||||||
sweep_time,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_bits(&self) -> u16 {
|
|
||||||
((self.number_of_sweep_shifts as u16) & 0b111)
|
|
||||||
| ((1 - self.sound_direction.as_bits()) << 3) // sweep works backwards
|
|
||||||
| ((self.sweep_time as u16) & 0b111) << 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SweepSettings {
|
|
||||||
fn default() -> Self {
|
|
||||||
SweepSettings::new(0, SoundDirection::Increase, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EnvelopeSettings {
|
|
||||||
step_time: u8,
|
|
||||||
direction: SoundDirection,
|
|
||||||
initial_volume: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EnvelopeSettings {
|
|
||||||
pub fn new(step_time: u8, direction: SoundDirection, initial_volume: u8) -> Self {
|
|
||||||
assert!(step_time < 8, "Step time must be less than 8");
|
|
||||||
assert!(initial_volume < 16, "Initial volume must be less that 16");
|
|
||||||
EnvelopeSettings {
|
|
||||||
step_time,
|
|
||||||
direction,
|
|
||||||
initial_volume,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_bits(&self) -> u16 {
|
|
||||||
(self.step_time as u16) << 8
|
|
||||||
| (self.direction.as_bits() << 11)
|
|
||||||
| ((self.initial_volume as u16) << 12)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for EnvelopeSettings {
|
|
||||||
fn default() -> Self {
|
|
||||||
EnvelopeSettings::new(0, SoundDirection::Increase, 15)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum DutyCycle {
|
|
||||||
OneEighth,
|
|
||||||
OneQuarter,
|
|
||||||
Half,
|
|
||||||
ThreeQuarters,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DutyCycle {
|
|
||||||
fn as_bits(&self) -> u16 {
|
|
||||||
use DutyCycle::*;
|
|
||||||
|
|
||||||
match &self {
|
|
||||||
OneEighth => 0,
|
|
||||||
OneQuarter => 1,
|
|
||||||
Half => 2,
|
|
||||||
ThreeQuarters => 3,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue