From 8e83976638f13ba6fd62ed5399585936cae7d786 Mon Sep 17 00:00:00 2001 From: Senryoku Date: Sun, 14 Jul 2024 23:36:58 +0200 Subject: [PATCH] Apply Amplitude envelope Making bios menu a lot more pleasant :) --- Readme.md | 6 +++- src/aica.zig | 84 ++++++++++++++++++++++++++++++++++------------------ 2 files changed, 60 insertions(+), 30 deletions(-) diff --git a/Readme.md b/Readme.md index fdf83d85..e9551cfb 100644 --- a/Readme.md +++ b/Readme.md @@ -30,7 +30,9 @@ You will also need to provide copies of `dc_boot.bin` and `dc_flash.bin` files i - What's "Maple V blank over interrupt"? "This interrupt is generated when a Maple interface transmission/reception operation spans V-Blank_In." - VMU/Storage -- The whole AICA Chip. Please have mercy. +- AICA: + - Stereo + - DSP - Renderer: - Modifier Volumes. - Implemented: Inclusion volumes and shadow bit over opaque geometry. @@ -71,6 +73,8 @@ You will also need to provide copies of `dc_boot.bin` and `dc_flash.bin` files i ## Thanks - Huge thanks to drk||Raziel and MetalliC for their respective contributions to the scene, and for answering my questions! +- Thanks to originaldave_ for the sh4 tests (https://github.com/SingleStepTests/sh4) +- And overall thanks to everyone participating in the EmuDev Discord :) ## Licence diff --git a/src/aica.zig b/src/aica.zig index 9f821a19..9f3a0c6c 100644 --- a/src/aica.zig +++ b/src/aica.zig @@ -70,12 +70,25 @@ pub const LFOControl = packed struct(u32) { _: u16, }; +pub const DirectPanVolSend = packed struct(u32) { + pan: u5, // Direct send pan (DIPAN) + // 0x00 or 0x10 is center, 0x0F is full right, 0x1F is full left + // 0x00-0x0F: each step beyond 0x00 attenuates the left side by 3db + // (right side remains at same volume) + // 0x10-0x1F: each step beyond 0x10 attenuates the right side by 3db + // (left side remains at same volume) + _0: u3, + volume: u4, // Direct send volume (DISDL) - "Affects how much of this channel is being sent directly to the dry output. 0xF is full volume (no attenuation), every level beneath that adds 3dB, and 0x0 means no output."" + _1: u4, + _2: u16, +}; + pub const LPF1Volume = packed struct(u32) { q: u5, // filter resonance value Q = (0.75*value)-3, from -3 to 20.25 db lpoff: bool, // 1 = turn off lowpass filter. voff: bool, // if this bit is set to 1, the constant attenuation, envelope, and LFO volumes will not take effect. however, the note will still end when the envelope level reaches zero in the release state. _: u1, // unknown [SAVED] - constant_attenuation: u8, // this value *4 seems to be added to the envelope attenuation (as in, 0x00-0xFF here corresponds to 0x000-0x3FF when referring to the envelope attenuation) + constant_attenuation: u8, // (TL) this value *4 seems to be added to the envelope attenuation (as in, 0x00-0xFF here corresponds to 0x000-0x3FF when referring to the envelope attenuation) _r: u16, }; @@ -101,24 +114,24 @@ pub const LPF8 = packed struct(u32) { // NOTE: Only the lower 16bits of each registers are actually used. pub const AICAChannel = packed struct(u576) { - play_control: PlayControl, - sample_address_low: u32, // lowest 16 bits of sample start address (SA) (in bytes) - loop_start: u32, - loop_end: u32, - amp_env_1: AmpEnv1, - amp_env_2: AmpEnv2, - sample_pitch_rate: SamplePitchRate, - lfo_control: LFOControl, - dps_channel_send: u32, - direct_pan_vol_send: u32, - lpf1_volume: LPF1Volume, - lpf2: u32, // Bits 0-12: Filter value - lpf3: u32, // Bits 0-12: Filter value - lpf4: u32, // Bits 0-12: Filter value - lpf5: u32, // Bits 0-12: Filter value - lpf6: u32, // Bits 0-12: Filter value - lpf7: LPF7, - lpf8: LPF8, + play_control: PlayControl, // 0x00 + sample_address_low: u32, // 0x04 Lowest 16 bits of sample start address (SA) (in bytes) + loop_start: u32, // 0x08 + loop_end: u32, // 0x0C + amp_env_1: AmpEnv1, // 0x10 + amp_env_2: AmpEnv2, // 0x14 + sample_pitch_rate: SamplePitchRate, // 0x18 + lfo_control: LFOControl, // 0x1C + dps_channel_send: DirectPanVolSend, // 0x20 + direct_pan_vol_send: u32, // 0x24 + lpf1_volume: LPF1Volume, // 0x28 + lpf2: u32, // 0x2C Bits 0-12: Filter value (FLV0) + lpf3: u32, // 0x30 Bits 0-12: Filter value (FLV1) + lpf4: u32, // 0x34 Bits 0-12: Filter value (FLV2) + lpf5: u32, // 0x38 Bits 0-12: Filter value (FLV3) + lpf6: u32, // 0x3C Bits 0-12: Filter value (FLV4) + lpf7: LPF7, // 0x40 + lpf8: LPF8, // 0x44 pub fn sample_address(self: *const AICAChannel) u32 { return (@as(u32, self.play_control.start_address) << 16) | (self.sample_address_low & 0xFFFF); @@ -370,7 +383,7 @@ const EnvelopeDecayValue = [_][4]u4{ pub const AICA = struct { pub const SampleRate = 44100; - const ARM7CycleRatio = 9; + const ARM7CycleRatio = 80; const SH4CyclesPerSample = @divTrunc(200_000_000, SampleRate); // FIXME? const SH4CyclesPerSubSample = 448; @@ -632,14 +645,6 @@ pub const AICA = struct { self.get_reg(u32, .INTRequest).* = 0; self.check_interrupts(); - @memset(&self.arm7.r, 0); - @memset(&self.arm7.r_fiq_8_12, 0); - @memset(&self.arm7.r_usr, 0); - @memset(&self.arm7.r_fiq, 0); - @memset(&self.arm7.r_svc, 0); - @memset(&self.arm7.r_irq, 0); - @memset(&self.arm7.r_abt, 0); - @memset(&self.arm7.r_und, 0); self._arm_cycles_counter = 0; self.arm7.reset(value & 1 == 0); if (value & 1 == 0 and self.wave_memory[0] == 0x00000000) { @@ -876,8 +881,8 @@ pub const AICA = struct { // FIXME: We're not actually counting ARM7 cycles here (unless all instructions are 1 cycle :^)). while (self._arm_cycles_counter >= ARM7CycleRatio) { self._arm_cycles_counter -= ARM7CycleRatio; - // aica_log.info("arm7: ({s}) [{X:0>4}] {X:0>8} - {s: <22} - SP:{X:0>8} - LR:{X:0>8}", .{ @tagName(self.arm7.cpsr.m), @max(4, self.arm7.pc() & self.arm7.memory_address_mask) - 4, self.arm7.instruction_pipeline[0], arm7.ARM7.disassemble(self.arm7.instruction_pipeline[0]), self.arm7.sp(), self.arm7.lr() }); arm7.interpreter.tick(&self.arm7); + if (self.arm7.pc() >= 0x00200000) { aica_log.warn("arm7: PC out of bounds: {X:0>8}, stopping.", .{self.arm7.pc()}); self.arm7.running = false; @@ -1023,6 +1028,27 @@ pub const AICA = struct { // else => 0, }; + if (!registers.lpf1_volume.lpoff) { + // TODO! + } + + // Apply amplitude envelope + if (!registers.lpf1_volume.voff) { + var attenuation: i32 = registers.lpf1_volume.constant_attenuation; + attenuation <<= 2; + attenuation += state.amp_env_level; + if (registers.lfo_control.amplitude_modulation_depth != 0) { + // TODO + } + if (attenuation >= 0x3C0) { + state.curr_sample = 0; + } else { + const linearvol = ((attenuation & 0x3F) ^ 0x7F) + 1; + state.curr_sample *|= linearvol; + state.curr_sample >>= @intCast(7 + (attenuation >> 6)); + } + } + state.play_position +%= 1; if (state.play_position == registers.loop_end & 0xFFFF) {