diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/APU.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/APU.cs index 91c9abb981..d8569e23f6 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/APU.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/APU.cs @@ -923,6 +923,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo } MetaspuSoundProvider metaspu = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V); + public int MaxVolume { get; set; } // not supported void ISoundProvider.GetSamples(short[] samples) { diff --git a/BizHawk.Emulation/Consoles/PC Engine/ADPCM.cs b/BizHawk.Emulation/Consoles/PC Engine/ADPCM.cs index 8337e410e8..25d7720f95 100644 --- a/BizHawk.Emulation/Consoles/PC Engine/ADPCM.cs +++ b/BizHawk.Emulation/Consoles/PC Engine/ADPCM.cs @@ -3,27 +3,31 @@ using BizHawk.Emulation.Sound; namespace BizHawk.Emulation.Consoles.TurboGrafx { - public partial class PCEngine + public sealed class ADPCM { - // herein I begin to realize the downside of prodigous use of partial class. - // too much stuff in one namespace. - // partial class still rocks though. you rock, partial class. - public ushort adpcm_io_address; public ushort adpcm_read_address; public ushort adpcm_write_address; public ushort adpcm_length; - public int adpcm_read_timer, adpcm_write_timer; + public int adpcm_read_timer, adpcm_write_timer; public byte adpcm_read_buffer, adpcm_write_buffer; public bool adpcm_read_pending, adpcm_write_pending; + public byte[] RAM = new byte[0x10000]; + public MetaspuSoundProvider SoundProvider = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V); + long LastThink; float adpcm_playback_timer; + + ScsiCDBus SCSI; + PCEngine pce; - public byte[] ADPCM_RAM; - - public MetaspuSoundProvider ADPCM_Provider; + public ADPCM(PCEngine pcEngine, ScsiCDBus scsi) + { + pce = pcEngine; + SCSI = scsi; + } static readonly int[] StepSize = { @@ -47,7 +51,7 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx public void AdpcmControlWrite(byte value) { Log.Error("CD","ADPCM CONTROL WRITE {0:X2}",value); - if ((CdIoPorts[0x0D] & 0x80) != 0 && (value & 0x80) == 0) + if ((Port180D & 0x80) != 0 && (value & 0x80) == 0) { Log.Note("CD", "Reset ADPCM!"); adpcm_read_address = 0; @@ -67,7 +71,7 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx adpcm_read_address--; } - if ((CdIoPorts[0x0D] & 2) == 0 && (value & 2) != 0) + if ((Port180D & 2) == 0 && (value & 2) != 0) { adpcm_write_address = adpcm_io_address; if ((value & 1) == 0) @@ -92,23 +96,10 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx adpcm_playback_timer = 0; magnitude = 0; } - } - public void AdpcmDataWrite(byte value) - { - adpcm_write_buffer = value; - adpcm_write_timer = 24; - adpcm_write_pending = true; + Port180D = value; } - public byte AdpcmDataRead() - { - adpcm_read_pending = true; - adpcm_read_timer = 24; - return adpcm_read_buffer; - } - - public bool AdpcmIsPlaying { get; private set; } public bool AdpcmBusyWriting { get { return AdpcmCdDmaRequested; } } public bool AdpcmBusyReading { get { return adpcm_read_pending; } } @@ -127,10 +118,10 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx byte sample; if (nibble == false) { - sample = (byte) (ADPCM_RAM[adpcm_read_address] >> 4); + sample = (byte) (RAM[adpcm_read_address] >> 4); nibble = true; } else { - sample = (byte)(ADPCM_RAM[adpcm_read_address] & 0xF); + sample = (byte)(RAM[adpcm_read_address] & 0xF); nibble = false; adpcm_length--; adpcm_read_address++; @@ -151,10 +142,10 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx void AdpcmEmitSample() { if (AdpcmIsPlaying == false) - ADPCM_Provider.buffer.enqueue_sample(0, 0); + SoundProvider.buffer.enqueue_sample(0, 0); else { - int rate = 16 - (CdIoPorts[0x0E] & 0x0F); + int rate = 16 - (Port180E & 0x0F); float khz = 32 / rate; if (nextSampleTimer == 0) @@ -170,14 +161,14 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx } short adjustedSample = (short)((playingSample - 2048) << 3); - ADPCM_Provider.buffer.enqueue_sample(adjustedSample, adjustedSample); + SoundProvider.buffer.enqueue_sample(adjustedSample, adjustedSample); } } - public void AdpcmThink() + public void Think() { - int cycles = (int) (Cpu.TotalExecutedCycles - LastThink); - LastThink = Cpu.TotalExecutedCycles; + int cycles = (int) (pce.Cpu.TotalExecutedCycles - LastThink); + LastThink = pce.Cpu.TotalExecutedCycles; adpcm_playback_timer -= cycles; if (adpcm_playback_timer < 0) @@ -191,7 +182,7 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx if (adpcm_read_pending && adpcm_read_timer <= 0) { - adpcm_read_buffer = ADPCM_RAM[adpcm_read_address++]; + adpcm_read_buffer = RAM[adpcm_read_address++]; adpcm_read_pending = false; if (adpcm_length > ushort.MinValue) adpcm_length--; @@ -199,7 +190,7 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx if (adpcm_write_pending && adpcm_write_timer <= 0) { - ADPCM_RAM[adpcm_write_address++] = adpcm_write_buffer; + RAM[adpcm_write_address++] = adpcm_write_buffer; adpcm_write_pending = false; if (adpcm_length < ushort.MaxValue) adpcm_length++; @@ -210,7 +201,7 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx if (SCSI.REQ && SCSI.IO && !SCSI.CD && !SCSI.ACK) { byte dmaByte = SCSI.DataBits; - ADPCM_RAM[adpcm_write_address++] = dmaByte; + RAM[adpcm_write_address++] = dmaByte; SCSI.ACK = false; SCSI.REQ = false; @@ -219,16 +210,37 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx if (SCSI.DataTransferInProgress == false) { - CdIoPorts[0x0B] = 0; + Port180B = 0; Console.WriteLine(" ADPCM DMA COMPLETED"); } } - CdIoPorts[0x03] &= 0xF3; - if (AdpcmIsPlaying == false) CdIoPorts[0x03] |= 0x08; - RefreshIRQ2(); + pce.IRQ2Monitor &= 0xF3; + if (AdpcmIsPlaying == false) pce.IRQ2Monitor |= 0x08; + pce.RefreshIRQ2(); } - bool AdpcmCdDmaRequested { get { return (CdIoPorts[0x0B] & 3) != 0; } } + public bool AdpcmCdDmaRequested { get { return (Port180B & 3) != 0; } } + + public byte Port180A + { + set + { + adpcm_write_buffer = value; + adpcm_write_timer = 24; + adpcm_write_pending = true; + } + + get + { + adpcm_read_pending = true; + adpcm_read_timer = 24; + return adpcm_read_buffer; + } + } + + public byte Port180B; + public byte Port180D; + public byte Port180E; } } diff --git a/BizHawk.Emulation/Consoles/PC Engine/PCEngine.cs b/BizHawk.Emulation/Consoles/PC Engine/PCEngine.cs index 0a94321229..db51cbf454 100644 --- a/BizHawk.Emulation/Consoles/PC Engine/PCEngine.cs +++ b/BizHawk.Emulation/Consoles/PC Engine/PCEngine.cs @@ -8,12 +8,7 @@ using BizHawk.DiscSystem; namespace BizHawk.Emulation.Consoles.TurboGrafx { - public enum NecSystemType - { - TurboGrafx, - TurboCD, - SuperGrafx - } + public enum NecSystemType { TurboGrafx, TurboCD, SuperGrafx } public sealed partial class PCEngine : IEmulator { @@ -29,10 +24,10 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx public VCE VCE; public VPC VPC; public ScsiCDBus SCSI; - + public ADPCM ADPCM; + public HuC6280PSG PSG; public CDAudio CDAudio; - // TODO ADPCM public SoundMixer SoundMixer; public MetaspuSoundProvider SoundSynchronizer; @@ -109,16 +104,16 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx { Ram = new byte[0x2000]; CDRam = new byte[0x10000]; - ADPCM_RAM = new byte[0x10000]; + ADPCM = new ADPCM(this, SCSI); Cpu.ReadMemory21 = ReadMemoryCD; Cpu.WriteMemory21 = WriteMemoryCD; Cpu.WriteVDC = VDC1.WriteVDC; - CDAudio = new CDAudio(disc, short.MaxValue); - ADPCM_Provider = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V); - SoundMixer = new SoundMixer(PSG, CDAudio, ADPCM_Provider); + CDAudio = new CDAudio(disc); + PSG.MaxVolume = short.MaxValue * 3 / 4; + SoundMixer = new SoundMixer(PSG, CDAudio, ADPCM.SoundProvider); SoundSynchronizer = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V); soundProvider = SoundSynchronizer; - Cpu.ThinkAction = () => { SCSI.Think(); AdpcmThink(); }; + Cpu.ThinkAction = () => { SCSI.Think(); ADPCM.Think(); }; } if (rom.Length == 0x60000) @@ -175,6 +170,12 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx VDC2.PerformSpriteLimit = true; } + if (game["CdVol"]) + CDAudio.MaxVolume = int.Parse(game.OptionValue("CdVol")); + if (game["PsgVol"]) + PSG.MaxVolume = int.Parse(game.OptionValue("PsgVol")); + // TODO ADPCM + // Ok, yes, HBlankPeriod's only purpose is game-specific hax. // 1) At least they're not coded directly into the emulator, but instead data-driven. // 2) The games which have custom HBlankPeriods work without it, the override only @@ -365,7 +366,7 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx if (TurboCD) { writer.Write(CDRam); - writer.Write(ADPCM_RAM); + writer.Write(ADPCM.RAM); } writer.Write(Frame); writer.Write(_lagcount); @@ -407,7 +408,7 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx if (TurboCD) { CDRam = reader.ReadBytes(0x10000); - ADPCM_RAM = reader.ReadBytes(0x10000); + ADPCM.RAM = reader.ReadBytes(0x10000); } Frame = reader.ReadInt32(); _lagcount = reader.ReadInt32(); @@ -482,9 +483,9 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx (addr, value) => CDRam[addr & 0xFFFF] = value); domains.Add(CDRamMemoryDomain); - var AdpcmMemoryDomain = new MemoryDomain("ADPCM RAM", ADPCM_RAM.Length, Endian.Little, - addr => ADPCM_RAM[addr & 0xFFFF], - (addr, value) => ADPCM_RAM[addr & 0xFFFF] = value); + var AdpcmMemoryDomain = new MemoryDomain("ADPCM RAM", ADPCM.RAM.Length, Endian.Little, + addr => ADPCM.RAM[addr & 0xFFFF], + (addr, value) => ADPCM.RAM[addr & 0xFFFF] = value); domains.Add(AdpcmMemoryDomain); if (SuperRam != null) diff --git a/BizHawk.Emulation/Consoles/PC Engine/TurboCD.cs b/BizHawk.Emulation/Consoles/PC Engine/TurboCD.cs index f9c0845041..1c574684c1 100644 --- a/BizHawk.Emulation/Consoles/PC Engine/TurboCD.cs +++ b/BizHawk.Emulation/Consoles/PC Engine/TurboCD.cs @@ -11,9 +11,12 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx { public partial class PCEngine { - private byte[] CdIoPorts = new byte[16]; + byte[] CdIoPorts = new byte[16]; - private void InitScsiBus() + public byte IRQ2Control { get { return CdIoPorts[2]; } set { CdIoPorts[2] = value; } } + public byte IRQ2Monitor { get { return CdIoPorts[3]; } set { CdIoPorts[3] = value; } } + + void InitScsiBus() { SCSI = new ScsiCDBus(this, disc); // this is kind of stupid @@ -38,7 +41,7 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx }; } - private void WriteCD(int addr, byte value) + void WriteCD(int addr, byte value) { //Log.Error("CD","Write: {0:X4} {1:X2} (PC={2:X4})", addr & 0x1FFF, value, Cpu.PC); switch (addr & 0x1FFF) @@ -103,45 +106,44 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx break; case 0x1808: // ADPCM address LSB - adpcm_io_address &= 0xFF00; - adpcm_io_address |= value; + ADPCM.adpcm_io_address &= 0xFF00; + ADPCM.adpcm_io_address |= value; if ((CdIoPorts[0x0D] & 0x10) != 0) { Console.WriteLine("doing silly thing"); - adpcm_length = adpcm_io_address; + ADPCM.adpcm_length = ADPCM.adpcm_io_address; } - Log.Error("CD", "adpcm address = {0:X4}", adpcm_io_address); + Log.Error("CD", "adpcm address = {0:X4}", ADPCM.adpcm_io_address); break; case 0x1809: // ADPCM address MSB - adpcm_io_address &= 0x00FF; - adpcm_io_address |= (ushort)(value << 8); + ADPCM.adpcm_io_address &= 0x00FF; + ADPCM.adpcm_io_address |= (ushort)(value << 8); if ((CdIoPorts[0x0D] & 0x10) != 0) { Console.WriteLine("doing silly thing"); - adpcm_length = adpcm_io_address; + ADPCM.adpcm_length = ADPCM.adpcm_io_address; } - Log.Error("CD", "adpcm address = {0:X4}", adpcm_io_address); + Log.Error("CD", "adpcm address = {0:X4}", ADPCM.adpcm_io_address); break; case 0x180A: // ADPCM Memory Read/Write Port - AdpcmDataWrite(value); + ADPCM.Port180A = value; break; case 0x180B: // ADPCM DMA Control - CdIoPorts[0x0B] = value; + ADPCM.Port180B = value; Log.Error("CD", "Write to ADPCM DMA Control [B] {0:X2}", value); - if (AdpcmCdDmaRequested) + if (ADPCM.AdpcmCdDmaRequested) Console.WriteLine(" ADPCM DMA REQUESTED"); break; case 0x180D: // ADPCM Address Control - AdpcmControlWrite(value); - CdIoPorts[0x0D] = value; + ADPCM.AdpcmControlWrite(value); break; case 0x180E: // ADPCM Playback Rate - CdIoPorts[0x0E] = value; + ADPCM.Port180E = value; Log.Error("CD", "Write to ADPCM Sample Rate [E] {0:X2}", value); break; @@ -238,21 +240,21 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx return returnValue; case 0x180A: // ADPCM Memory Read/Write Port - return AdpcmDataRead(); + return ADPCM.Port180A; case 0x180B: // ADPCM Data Transfer Control //Log.Error("CD", "Read ADPCM Data Transfer Control"); - return CdIoPorts[0x0B]; + return ADPCM.Port180B; case 0x180C: // ADPCM Status returnValue = 0; - if (AdpcmIsPlaying) + if (ADPCM.AdpcmIsPlaying) returnValue |= 0x08; else returnValue |= 0x01; - if (AdpcmBusyWriting) + if (ADPCM.AdpcmBusyWriting) returnValue |= 0x04; - if (AdpcmBusyReading) + if (ADPCM.AdpcmBusyReading) returnValue |= 0x80; //Log.Error("CD", "Read ADPCM Status {0:X2}", returnValue); @@ -281,7 +283,7 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx } } - private void RefreshIRQ2() + public void RefreshIRQ2() { int mask = CdIoPorts[2] & CdIoPorts[3] & 0x7C; Cpu.IRQ2Assert = (mask != 0); diff --git a/BizHawk.Emulation/ExternalCores/PsxCore.cs b/BizHawk.Emulation/ExternalCores/PsxCore.cs index 2e7297de7b..c97dc960c7 100644 --- a/BizHawk.Emulation/ExternalCores/PsxCore.cs +++ b/BizHawk.Emulation/ExternalCores/PsxCore.cs @@ -92,6 +92,7 @@ namespace BizHawk public byte[] SaveStateBinary() { return new byte[1]; } public void GetSamples(short[] samples) { } public void DiscardSamples() { } + public int MaxVolume { get; set; } private IList memoryDomains; public IList MemoryDomains { get { return memoryDomains; } } public MemoryDomain MainMemory { get { return memoryDomains[0]; } } diff --git a/BizHawk.Emulation/Interfaces/Base Implementations/NullEmulator.cs b/BizHawk.Emulation/Interfaces/Base Implementations/NullEmulator.cs index 50c7967164..b2bf56677d 100644 --- a/BizHawk.Emulation/Interfaces/Base Implementations/NullEmulator.cs +++ b/BizHawk.Emulation/Interfaces/Base Implementations/NullEmulator.cs @@ -55,6 +55,7 @@ namespace BizHawk public int BackgroundColor { get { return 0; } } public void GetSamples(short[] samples) { } public void DiscardSamples() { } + public int MaxVolume { get; set; } private IList memoryDomains; public IList MemoryDomains { get { return memoryDomains; } } public MemoryDomain MainMemory { get { return memoryDomains[0]; } } @@ -67,5 +68,6 @@ namespace BizHawk public void GetSamples(short[] samples) { } public void DiscardSamples() { } + public int MaxVolume { get; set; } } } diff --git a/BizHawk.Emulation/Interfaces/ISoundProvider.cs b/BizHawk.Emulation/Interfaces/ISoundProvider.cs index dee10b6a81..6b1d23f27a 100644 --- a/BizHawk.Emulation/Interfaces/ISoundProvider.cs +++ b/BizHawk.Emulation/Interfaces/ISoundProvider.cs @@ -4,5 +4,6 @@ { void GetSamples(short[] samples); void DiscardSamples(); + int MaxVolume { get; set; } } } diff --git a/BizHawk.Emulation/Notes.txt b/BizHawk.Emulation/Notes.txt index 2be6504090..27d634c2aa 100644 --- a/BizHawk.Emulation/Notes.txt +++ b/BizHawk.Emulation/Notes.txt @@ -31,6 +31,9 @@ BizHawk.Emulation Project Notes Open the CpuCoreGenerator solution and make your changes there. Run the program to regenerate your cpus. CpuCoreGenerator is a separate solution and it may not be obvious that it is there. +7) The new new plan is really for cores to be native. Which was actually the original plan. + Note that native doesn't _necessarily_ mean C++. + --------------- Emulation Notes --------------- @@ -43,8 +46,6 @@ Emulation Notes Feedback is appreciated. ISoundProvider: - * A means of controlling overall volume for the SoundProvider would be beneficial - when it comes to mixing multiple ISoundProviders. * Current implementations generate garbage on each frame, although the API does not _require_ garbage to be created, it encourages it because you cannot provide a large buffer and specify a smaller number of samples to fill. Also the BufferAsync generates garbage and probably the @@ -57,8 +58,6 @@ Emulation Notes * I suppose NTSC/PAL (ie: target fps) could be useful also. IEmulator: - * The LoadGame(IGame) is potentially problematic. Perhaps we simply remove this. Most likely - the constructor would then take an IGame as a requirement (or ICDImage or IRomSet or IFileSystem...) * IEmulator should provide metadata about what Options it recognizes. * Possibly, a lot of the metadata type functions should be removed from IEmulator and added to new - interface (such as IConsole, IArcade, IComputer, whatever) + interface (such as IConsole, IArcade, IComputer, whatever) \ No newline at end of file diff --git a/BizHawk.Emulation/Sound/CDAudio.cs b/BizHawk.Emulation/Sound/CDAudio.cs index b419d974ba..beb18d2e82 100644 --- a/BizHawk.Emulation/Sound/CDAudio.cs +++ b/BizHawk.Emulation/Sound/CDAudio.cs @@ -32,7 +32,7 @@ namespace BizHawk.Emulation.Sound public CDAudioMode Mode = CDAudioMode.Stopped; public PlaybackMode PlayMode = PlaybackMode.LoopOnCompletion; - public int MaxVolume; + public int MaxVolume { get; set; } public int LogicalVolume = 100; public int StartLBA, EndLBA; @@ -45,7 +45,7 @@ namespace BizHawk.Emulation.Sound public int FadeOutOverFrames = 0; public int FadeOutFramesRemaining = 0; - public CDAudio(Disc disc, int maxVolume) + public CDAudio(Disc disc, int maxVolume = short.MaxValue) { Disc = disc; MaxVolume = maxVolume; @@ -150,10 +150,8 @@ namespace BizHawk.Emulation.Sound short left = (short)((SectorCache[sectorOffset + 1] << 8) | (SectorCache[sectorOffset + 0])); short right = (short)((SectorCache[sectorOffset + 3] << 8) | (SectorCache[sectorOffset + 2])); - // TODO absolute volume (for setting relative volumes between sound sources) - - samples[offset++] += (short) (left * LogicalVolume / 100); - samples[offset++] += (short) (right * LogicalVolume / 100); + samples[offset++] += (short) (left * LogicalVolume / 100 * MaxVolume / short.MaxValue); + samples[offset++] += (short) (right * LogicalVolume / 100 * MaxVolume / short.MaxValue); SectorOffset++; if (SectorOffset == 588) @@ -207,7 +205,6 @@ namespace BizHawk.Emulation.Sound { get { - // TODO apply the damn volume if (Mode != CDAudioMode.Playing) return 0; diff --git a/BizHawk.Emulation/Sound/HuC6280PSG.cs b/BizHawk.Emulation/Sound/HuC6280PSG.cs index 18b27af98e..126a7a4cfd 100644 --- a/BizHawk.Emulation/Sound/HuC6280PSG.cs +++ b/BizHawk.Emulation/Sound/HuC6280PSG.cs @@ -27,21 +27,23 @@ namespace BizHawk.Emulation.Sound public PSGChannel[] Channels = new PSGChannel[8]; public byte VoiceLatch; - private byte WaveTableWriteOffset; + byte WaveTableWriteOffset; - private Queue commands = new Queue(256); - private long frameStartTime, frameStopTime; + Queue commands = new Queue(256); + long frameStartTime, frameStopTime; - private const int SampleRate = 44100; - private const int PsgBase = 3580000; - private static byte[] LogScale = { 0, 0, 10, 10, 13, 13, 16, 16, 20, 20, 26, 26, 32, 32, 40, 40, 51, 51, 64, 64, 81, 81, 102, 102, 128, 128, 161, 161, 203, 203, 255, 255 }; - private static byte[] VolumeReductionTable = { 0x1F, 0x1D, 0x1B, 0x19, 0x17, 0x15, 0x13, 0x10, 0x0F, 0x0D, 0x0B, 0x09, 0x07, 0x05, 0x03, 0x00 }; + const int SampleRate = 44100; + const int PsgBase = 3580000; + static byte[] LogScale = { 0, 0, 10, 10, 13, 13, 16, 16, 20, 20, 26, 26, 32, 32, 40, 40, 51, 51, 64, 64, 81, 81, 102, 102, 128, 128, 161, 161, 203, 203, 255, 255 }; + static byte[] VolumeReductionTable = { 0x1F, 0x1D, 0x1B, 0x19, 0x17, 0x15, 0x13, 0x10, 0x0F, 0x0D, 0x0B, 0x09, 0x07, 0x05, 0x03, 0x00 }; public byte MainVolumeLeft; public byte MainVolumeRight; + public int MaxVolume { get; set; } public HuC6280PSG() { + MaxVolume = short.MaxValue; Waves.InitWaves(); for (int i=0; i<8; i++) Channels[i] = new PSGChannel(); @@ -129,7 +131,7 @@ namespace BizHawk.Emulation.Sound } } - public void DiscardSamples() { /*TBD*/ } + public void DiscardSamples() { } public void GetSamples(short[] samples) { int elapsedCycles = (int) (frameStopTime - frameStartTime); @@ -193,8 +195,8 @@ namespace BizHawk.Emulation.Sound channel.SampleOffset %= wave.Length; short value = channel.DDA ? channel.DDAValue : wave[(int) channel.SampleOffset]; - samples[i++] += (short)(value * LogScale[volumeLeft] / 255f / 6f); - samples[i++] += (short)(value * LogScale[volumeRight] / 255f / 6f); + samples[i++] += (short)(value * LogScale[volumeLeft] / 255f / 6f * MaxVolume / short.MaxValue); + samples[i++] += (short)(value * LogScale[volumeRight] / 255f / 6f * MaxVolume / short.MaxValue); channel.SampleOffset += moveThroughWaveRate; channel.SampleOffset %= wave.Length; diff --git a/BizHawk.Emulation/Sound/SN76489.cs b/BizHawk.Emulation/Sound/SN76489.cs index bee86ba621..3bf7784106 100644 --- a/BizHawk.Emulation/Sound/SN76489.cs +++ b/BizHawk.Emulation/Sound/SN76489.cs @@ -24,7 +24,7 @@ namespace BizHawk.Emulation.Sound private const int SampleRate = 44100; private static byte[] LogScale = { 0, 10, 13, 16, 20, 26, 32, 40, 51, 64, 81, 102, 128, 161, 203, 255 }; - public void Mix(short[] samples, int start, int len) + public void Mix(short[] samples, int start, int len, int maxVolume) { if (Volume == 0) return; @@ -36,8 +36,8 @@ namespace BizHawk.Emulation.Sound { short value = Wave[(int)WaveOffset]; - samples[i++] += (short)(Left ? (value / 4 * LogScale[Volume] / 0x1FF) : 0); - samples[i++] += (short)(Right ? (value / 4 * LogScale[Volume] / 0x1FF) : 0); + samples[i++] += (short)(Left ? (value / 4 * LogScale[Volume] / 0xFF * maxVolume / short.MaxValue) : 0); + samples[i++] += (short)(Right ? (value / 4 * LogScale[Volume] / 0xFF * maxVolume / short.MaxValue) : 0); WaveOffset += moveThroughWaveRate; if (WaveOffset >= Wave.Length) WaveOffset %= Wave.Length; @@ -55,6 +55,7 @@ namespace BizHawk.Emulation.Sound public SN76489() { + MaxVolume = short.MaxValue * 2 / 3; Waves.InitWaves(); for (int i=0; i<4; i++) { @@ -416,7 +417,8 @@ namespace BizHawk.Emulation.Sound #endregion - public void DiscardSamples() { /* todo */ } + public int MaxVolume { get; set; } + public void DiscardSamples() { commands.Clear(); } public void GetSamples(short[] samples) { int elapsedCycles = frameStopTime - frameStartTime; @@ -435,7 +437,7 @@ namespace BizHawk.Emulation.Sound public void GetSamplesImmediate(short[] samples, int start, int len) { for (int i = 0; i < 4; i++) - Channels[i].Mix(samples, start, len); + Channels[i].Mix(samples, start, len, MaxVolume); } class QueuedCommand diff --git a/BizHawk.Emulation/Sound/Utilities/BufferedAsync.cs b/BizHawk.Emulation/Sound/Utilities/BufferedAsync.cs index 4b688d7027..599d519d9c 100644 --- a/BizHawk.Emulation/Sound/Utilities/BufferedAsync.cs +++ b/BizHawk.Emulation/Sound/Utilities/BufferedAsync.cs @@ -29,6 +29,8 @@ namespace BizHawk.Emulation.Sound BaseSoundProvider.DiscardSamples(); } + public int MaxVolume { get; set; } + public void GetSamples(short[] samples) { int samplesToGenerate = SamplesInOneFrame; diff --git a/BizHawk.Emulation/Sound/Utilities/Metaspu.cs b/BizHawk.Emulation/Sound/Utilities/Metaspu.cs index 34b7e0654c..07a088f208 100644 --- a/BizHawk.Emulation/Sound/Utilities/Metaspu.cs +++ b/BizHawk.Emulation/Sound/Utilities/Metaspu.cs @@ -32,7 +32,9 @@ namespace BizHawk.Emulation.Sound { buffer.clear(); } - } + + public int MaxVolume { get; set; } + } public interface ISynchronizingAudioBuffer { diff --git a/BizHawk.Emulation/Sound/Utilities/SoundMixer.cs b/BizHawk.Emulation/Sound/Utilities/SoundMixer.cs index 55a546af8d..450caaecf0 100644 --- a/BizHawk.Emulation/Sound/Utilities/SoundMixer.cs +++ b/BizHawk.Emulation/Sound/Utilities/SoundMixer.cs @@ -3,7 +3,6 @@ namespace BizHawk.Emulation.Sound { // This is a straightforward class to mix/chain multiple ISoundProvider sources. - // TODO: Fine-tuned volume control would be a good thing. public sealed class SoundMixer : ISoundProvider { @@ -35,5 +34,16 @@ namespace BizHawk.Emulation.Sound foreach (var soundSource in SoundProviders) soundSource.GetSamples(samples); } + + // Splits the volume space equally between available sources. + public void EqualizeVolumes() + { + int eachVolume = short.MaxValue / SoundProviders.Count; + foreach (var source in SoundProviders) + source.MaxVolume = eachVolume; + } + + // Not actually supported on mixer. + public int MaxVolume { get; set; } } } diff --git a/BizHawk.Emulation/Sound/YM2413.cs b/BizHawk.Emulation/Sound/YM2413.cs index 680687d714..e37cef87b5 100644 --- a/BizHawk.Emulation/Sound/YM2413.cs +++ b/BizHawk.Emulation/Sound/YM2413.cs @@ -18,6 +18,7 @@ namespace BizHawk.Emulation.Sound public YM2413() { + MaxVolume = short.MaxValue; opll = OPLL_new(3579545, 44100); } @@ -39,11 +40,12 @@ namespace BizHawk.Emulation.Sound } public void DiscardSamples() { } + public int MaxVolume { get; set; } public void GetSamples(short[] samples) { for (int i=0; i