Max volume is settable per sound source to enable mixing many sound sources without clipping. Potentially can be used to disable individual sound sources.

This commit is contained in:
beirich 2011-09-04 04:38:11 +00:00
parent ed7cc01d9f
commit 647cae698e
16 changed files with 151 additions and 117 deletions

View File

@ -923,6 +923,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
} }
MetaspuSoundProvider metaspu = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V); MetaspuSoundProvider metaspu = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V);
public int MaxVolume { get; set; } // not supported
void ISoundProvider.GetSamples(short[] samples) void ISoundProvider.GetSamples(short[] samples)
{ {

View File

@ -3,27 +3,31 @@ using BizHawk.Emulation.Sound;
namespace BizHawk.Emulation.Consoles.TurboGrafx 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_io_address;
public ushort adpcm_read_address; public ushort adpcm_read_address;
public ushort adpcm_write_address; public ushort adpcm_write_address;
public ushort adpcm_length; 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 byte adpcm_read_buffer, adpcm_write_buffer;
public bool adpcm_read_pending, adpcm_write_pending; public bool adpcm_read_pending, adpcm_write_pending;
public byte[] RAM = new byte[0x10000];
public MetaspuSoundProvider SoundProvider = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V);
long LastThink; long LastThink;
float adpcm_playback_timer; float adpcm_playback_timer;
ScsiCDBus SCSI;
PCEngine pce;
public byte[] ADPCM_RAM; public ADPCM(PCEngine pcEngine, ScsiCDBus scsi)
{
public MetaspuSoundProvider ADPCM_Provider; pce = pcEngine;
SCSI = scsi;
}
static readonly int[] StepSize = static readonly int[] StepSize =
{ {
@ -47,7 +51,7 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
public void AdpcmControlWrite(byte value) public void AdpcmControlWrite(byte value)
{ {
Log.Error("CD","ADPCM CONTROL WRITE {0:X2}",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!"); Log.Note("CD", "Reset ADPCM!");
adpcm_read_address = 0; adpcm_read_address = 0;
@ -67,7 +71,7 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
adpcm_read_address--; adpcm_read_address--;
} }
if ((CdIoPorts[0x0D] & 2) == 0 && (value & 2) != 0) if ((Port180D & 2) == 0 && (value & 2) != 0)
{ {
adpcm_write_address = adpcm_io_address; adpcm_write_address = adpcm_io_address;
if ((value & 1) == 0) if ((value & 1) == 0)
@ -92,23 +96,10 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
adpcm_playback_timer = 0; adpcm_playback_timer = 0;
magnitude = 0; magnitude = 0;
} }
}
public void AdpcmDataWrite(byte value) Port180D = value;
{
adpcm_write_buffer = value;
adpcm_write_timer = 24;
adpcm_write_pending = true;
} }
public byte AdpcmDataRead()
{
adpcm_read_pending = true;
adpcm_read_timer = 24;
return adpcm_read_buffer;
}
public bool AdpcmIsPlaying { get; private set; } public bool AdpcmIsPlaying { get; private set; }
public bool AdpcmBusyWriting { get { return AdpcmCdDmaRequested; } } public bool AdpcmBusyWriting { get { return AdpcmCdDmaRequested; } }
public bool AdpcmBusyReading { get { return adpcm_read_pending; } } public bool AdpcmBusyReading { get { return adpcm_read_pending; } }
@ -127,10 +118,10 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
byte sample; byte sample;
if (nibble == false) if (nibble == false)
{ {
sample = (byte) (ADPCM_RAM[adpcm_read_address] >> 4); sample = (byte) (RAM[adpcm_read_address] >> 4);
nibble = true; nibble = true;
} else { } else {
sample = (byte)(ADPCM_RAM[adpcm_read_address] & 0xF); sample = (byte)(RAM[adpcm_read_address] & 0xF);
nibble = false; nibble = false;
adpcm_length--; adpcm_length--;
adpcm_read_address++; adpcm_read_address++;
@ -151,10 +142,10 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
void AdpcmEmitSample() void AdpcmEmitSample()
{ {
if (AdpcmIsPlaying == false) if (AdpcmIsPlaying == false)
ADPCM_Provider.buffer.enqueue_sample(0, 0); SoundProvider.buffer.enqueue_sample(0, 0);
else else
{ {
int rate = 16 - (CdIoPorts[0x0E] & 0x0F); int rate = 16 - (Port180E & 0x0F);
float khz = 32 / rate; float khz = 32 / rate;
if (nextSampleTimer == 0) if (nextSampleTimer == 0)
@ -170,14 +161,14 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
} }
short adjustedSample = (short)((playingSample - 2048) << 3); 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); int cycles = (int) (pce.Cpu.TotalExecutedCycles - LastThink);
LastThink = Cpu.TotalExecutedCycles; LastThink = pce.Cpu.TotalExecutedCycles;
adpcm_playback_timer -= cycles; adpcm_playback_timer -= cycles;
if (adpcm_playback_timer < 0) if (adpcm_playback_timer < 0)
@ -191,7 +182,7 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
if (adpcm_read_pending && adpcm_read_timer <= 0) 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; adpcm_read_pending = false;
if (adpcm_length > ushort.MinValue) if (adpcm_length > ushort.MinValue)
adpcm_length--; adpcm_length--;
@ -199,7 +190,7 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
if (adpcm_write_pending && adpcm_write_timer <= 0) 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; adpcm_write_pending = false;
if (adpcm_length < ushort.MaxValue) if (adpcm_length < ushort.MaxValue)
adpcm_length++; adpcm_length++;
@ -210,7 +201,7 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
if (SCSI.REQ && SCSI.IO && !SCSI.CD && !SCSI.ACK) if (SCSI.REQ && SCSI.IO && !SCSI.CD && !SCSI.ACK)
{ {
byte dmaByte = SCSI.DataBits; byte dmaByte = SCSI.DataBits;
ADPCM_RAM[adpcm_write_address++] = dmaByte; RAM[adpcm_write_address++] = dmaByte;
SCSI.ACK = false; SCSI.ACK = false;
SCSI.REQ = false; SCSI.REQ = false;
@ -219,16 +210,37 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
if (SCSI.DataTransferInProgress == false) if (SCSI.DataTransferInProgress == false)
{ {
CdIoPorts[0x0B] = 0; Port180B = 0;
Console.WriteLine(" ADPCM DMA COMPLETED"); Console.WriteLine(" ADPCM DMA COMPLETED");
} }
} }
CdIoPorts[0x03] &= 0xF3; pce.IRQ2Monitor &= 0xF3;
if (AdpcmIsPlaying == false) CdIoPorts[0x03] |= 0x08; if (AdpcmIsPlaying == false) pce.IRQ2Monitor |= 0x08;
RefreshIRQ2(); 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;
} }
} }

View File

@ -8,12 +8,7 @@ using BizHawk.DiscSystem;
namespace BizHawk.Emulation.Consoles.TurboGrafx namespace BizHawk.Emulation.Consoles.TurboGrafx
{ {
public enum NecSystemType public enum NecSystemType { TurboGrafx, TurboCD, SuperGrafx }
{
TurboGrafx,
TurboCD,
SuperGrafx
}
public sealed partial class PCEngine : IEmulator public sealed partial class PCEngine : IEmulator
{ {
@ -29,10 +24,10 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
public VCE VCE; public VCE VCE;
public VPC VPC; public VPC VPC;
public ScsiCDBus SCSI; public ScsiCDBus SCSI;
public ADPCM ADPCM;
public HuC6280PSG PSG; public HuC6280PSG PSG;
public CDAudio CDAudio; public CDAudio CDAudio;
// TODO ADPCM
public SoundMixer SoundMixer; public SoundMixer SoundMixer;
public MetaspuSoundProvider SoundSynchronizer; public MetaspuSoundProvider SoundSynchronizer;
@ -109,16 +104,16 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
{ {
Ram = new byte[0x2000]; Ram = new byte[0x2000];
CDRam = new byte[0x10000]; CDRam = new byte[0x10000];
ADPCM_RAM = new byte[0x10000]; ADPCM = new ADPCM(this, SCSI);
Cpu.ReadMemory21 = ReadMemoryCD; Cpu.ReadMemory21 = ReadMemoryCD;
Cpu.WriteMemory21 = WriteMemoryCD; Cpu.WriteMemory21 = WriteMemoryCD;
Cpu.WriteVDC = VDC1.WriteVDC; Cpu.WriteVDC = VDC1.WriteVDC;
CDAudio = new CDAudio(disc, short.MaxValue); CDAudio = new CDAudio(disc);
ADPCM_Provider = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V); PSG.MaxVolume = short.MaxValue * 3 / 4;
SoundMixer = new SoundMixer(PSG, CDAudio, ADPCM_Provider); SoundMixer = new SoundMixer(PSG, CDAudio, ADPCM.SoundProvider);
SoundSynchronizer = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V); SoundSynchronizer = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V);
soundProvider = SoundSynchronizer; soundProvider = SoundSynchronizer;
Cpu.ThinkAction = () => { SCSI.Think(); AdpcmThink(); }; Cpu.ThinkAction = () => { SCSI.Think(); ADPCM.Think(); };
} }
if (rom.Length == 0x60000) if (rom.Length == 0x60000)
@ -175,6 +170,12 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
VDC2.PerformSpriteLimit = true; 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. // 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. // 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 // 2) The games which have custom HBlankPeriods work without it, the override only
@ -365,7 +366,7 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
if (TurboCD) if (TurboCD)
{ {
writer.Write(CDRam); writer.Write(CDRam);
writer.Write(ADPCM_RAM); writer.Write(ADPCM.RAM);
} }
writer.Write(Frame); writer.Write(Frame);
writer.Write(_lagcount); writer.Write(_lagcount);
@ -407,7 +408,7 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
if (TurboCD) if (TurboCD)
{ {
CDRam = reader.ReadBytes(0x10000); CDRam = reader.ReadBytes(0x10000);
ADPCM_RAM = reader.ReadBytes(0x10000); ADPCM.RAM = reader.ReadBytes(0x10000);
} }
Frame = reader.ReadInt32(); Frame = reader.ReadInt32();
_lagcount = reader.ReadInt32(); _lagcount = reader.ReadInt32();
@ -482,9 +483,9 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
(addr, value) => CDRam[addr & 0xFFFF] = value); (addr, value) => CDRam[addr & 0xFFFF] = value);
domains.Add(CDRamMemoryDomain); domains.Add(CDRamMemoryDomain);
var AdpcmMemoryDomain = new MemoryDomain("ADPCM RAM", ADPCM_RAM.Length, Endian.Little, var AdpcmMemoryDomain = new MemoryDomain("ADPCM RAM", ADPCM.RAM.Length, Endian.Little,
addr => ADPCM_RAM[addr & 0xFFFF], addr => ADPCM.RAM[addr & 0xFFFF],
(addr, value) => ADPCM_RAM[addr & 0xFFFF] = value); (addr, value) => ADPCM.RAM[addr & 0xFFFF] = value);
domains.Add(AdpcmMemoryDomain); domains.Add(AdpcmMemoryDomain);
if (SuperRam != null) if (SuperRam != null)

View File

@ -11,9 +11,12 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
{ {
public partial class PCEngine 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); SCSI = new ScsiCDBus(this, disc);
// this is kind of stupid // 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); //Log.Error("CD","Write: {0:X4} {1:X2} (PC={2:X4})", addr & 0x1FFF, value, Cpu.PC);
switch (addr & 0x1FFF) switch (addr & 0x1FFF)
@ -103,45 +106,44 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
break; break;
case 0x1808: // ADPCM address LSB case 0x1808: // ADPCM address LSB
adpcm_io_address &= 0xFF00; ADPCM.adpcm_io_address &= 0xFF00;
adpcm_io_address |= value; ADPCM.adpcm_io_address |= value;
if ((CdIoPorts[0x0D] & 0x10) != 0) if ((CdIoPorts[0x0D] & 0x10) != 0)
{ {
Console.WriteLine("doing silly thing"); 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; break;
case 0x1809: // ADPCM address MSB case 0x1809: // ADPCM address MSB
adpcm_io_address &= 0x00FF; ADPCM.adpcm_io_address &= 0x00FF;
adpcm_io_address |= (ushort)(value << 8); ADPCM.adpcm_io_address |= (ushort)(value << 8);
if ((CdIoPorts[0x0D] & 0x10) != 0) if ((CdIoPorts[0x0D] & 0x10) != 0)
{ {
Console.WriteLine("doing silly thing"); 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; break;
case 0x180A: // ADPCM Memory Read/Write Port case 0x180A: // ADPCM Memory Read/Write Port
AdpcmDataWrite(value); ADPCM.Port180A = value;
break; break;
case 0x180B: // ADPCM DMA Control case 0x180B: // ADPCM DMA Control
CdIoPorts[0x0B] = value; ADPCM.Port180B = value;
Log.Error("CD", "Write to ADPCM DMA Control [B] {0:X2}", value); Log.Error("CD", "Write to ADPCM DMA Control [B] {0:X2}", value);
if (AdpcmCdDmaRequested) if (ADPCM.AdpcmCdDmaRequested)
Console.WriteLine(" ADPCM DMA REQUESTED"); Console.WriteLine(" ADPCM DMA REQUESTED");
break; break;
case 0x180D: // ADPCM Address Control case 0x180D: // ADPCM Address Control
AdpcmControlWrite(value); ADPCM.AdpcmControlWrite(value);
CdIoPorts[0x0D] = value;
break; break;
case 0x180E: // ADPCM Playback Rate case 0x180E: // ADPCM Playback Rate
CdIoPorts[0x0E] = value; ADPCM.Port180E = value;
Log.Error("CD", "Write to ADPCM Sample Rate [E] {0:X2}", value); Log.Error("CD", "Write to ADPCM Sample Rate [E] {0:X2}", value);
break; break;
@ -238,21 +240,21 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
return returnValue; return returnValue;
case 0x180A: // ADPCM Memory Read/Write Port case 0x180A: // ADPCM Memory Read/Write Port
return AdpcmDataRead(); return ADPCM.Port180A;
case 0x180B: // ADPCM Data Transfer Control case 0x180B: // ADPCM Data Transfer Control
//Log.Error("CD", "Read ADPCM Data Transfer Control"); //Log.Error("CD", "Read ADPCM Data Transfer Control");
return CdIoPorts[0x0B]; return ADPCM.Port180B;
case 0x180C: // ADPCM Status case 0x180C: // ADPCM Status
returnValue = 0; returnValue = 0;
if (AdpcmIsPlaying) if (ADPCM.AdpcmIsPlaying)
returnValue |= 0x08; returnValue |= 0x08;
else else
returnValue |= 0x01; returnValue |= 0x01;
if (AdpcmBusyWriting) if (ADPCM.AdpcmBusyWriting)
returnValue |= 0x04; returnValue |= 0x04;
if (AdpcmBusyReading) if (ADPCM.AdpcmBusyReading)
returnValue |= 0x80; returnValue |= 0x80;
//Log.Error("CD", "Read ADPCM Status {0:X2}", returnValue); //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; int mask = CdIoPorts[2] & CdIoPorts[3] & 0x7C;
Cpu.IRQ2Assert = (mask != 0); Cpu.IRQ2Assert = (mask != 0);

View File

@ -92,6 +92,7 @@ namespace BizHawk
public byte[] SaveStateBinary() { return new byte[1]; } public byte[] SaveStateBinary() { return new byte[1]; }
public void GetSamples(short[] samples) { } public void GetSamples(short[] samples) { }
public void DiscardSamples() { } public void DiscardSamples() { }
public int MaxVolume { get; set; }
private IList<MemoryDomain> memoryDomains; private IList<MemoryDomain> memoryDomains;
public IList<MemoryDomain> MemoryDomains { get { return memoryDomains; } } public IList<MemoryDomain> MemoryDomains { get { return memoryDomains; } }
public MemoryDomain MainMemory { get { return memoryDomains[0]; } } public MemoryDomain MainMemory { get { return memoryDomains[0]; } }

View File

@ -55,6 +55,7 @@ namespace BizHawk
public int BackgroundColor { get { return 0; } } public int BackgroundColor { get { return 0; } }
public void GetSamples(short[] samples) { } public void GetSamples(short[] samples) { }
public void DiscardSamples() { } public void DiscardSamples() { }
public int MaxVolume { get; set; }
private IList<MemoryDomain> memoryDomains; private IList<MemoryDomain> memoryDomains;
public IList<MemoryDomain> MemoryDomains { get { return memoryDomains; } } public IList<MemoryDomain> MemoryDomains { get { return memoryDomains; } }
public MemoryDomain MainMemory { get { return memoryDomains[0]; } } public MemoryDomain MainMemory { get { return memoryDomains[0]; } }
@ -67,5 +68,6 @@ namespace BizHawk
public void GetSamples(short[] samples) { } public void GetSamples(short[] samples) { }
public void DiscardSamples() { } public void DiscardSamples() { }
public int MaxVolume { get; set; }
} }
} }

View File

@ -4,5 +4,6 @@
{ {
void GetSamples(short[] samples); void GetSamples(short[] samples);
void DiscardSamples(); void DiscardSamples();
int MaxVolume { get; set; }
} }
} }

View File

@ -31,6 +31,9 @@ BizHawk.Emulation Project Notes
Open the CpuCoreGenerator solution and make your changes there. Run the program to regenerate your cpus. 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. 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 Emulation Notes
--------------- ---------------
@ -43,8 +46,6 @@ Emulation Notes
Feedback is appreciated. Feedback is appreciated.
ISoundProvider: 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_ * 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 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 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. * I suppose NTSC/PAL (ie: target fps) could be useful also.
IEmulator: 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. * 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 * 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)

View File

@ -32,7 +32,7 @@ namespace BizHawk.Emulation.Sound
public CDAudioMode Mode = CDAudioMode.Stopped; public CDAudioMode Mode = CDAudioMode.Stopped;
public PlaybackMode PlayMode = PlaybackMode.LoopOnCompletion; public PlaybackMode PlayMode = PlaybackMode.LoopOnCompletion;
public int MaxVolume; public int MaxVolume { get; set; }
public int LogicalVolume = 100; public int LogicalVolume = 100;
public int StartLBA, EndLBA; public int StartLBA, EndLBA;
@ -45,7 +45,7 @@ namespace BizHawk.Emulation.Sound
public int FadeOutOverFrames = 0; public int FadeOutOverFrames = 0;
public int FadeOutFramesRemaining = 0; public int FadeOutFramesRemaining = 0;
public CDAudio(Disc disc, int maxVolume) public CDAudio(Disc disc, int maxVolume = short.MaxValue)
{ {
Disc = disc; Disc = disc;
MaxVolume = maxVolume; MaxVolume = maxVolume;
@ -150,10 +150,8 @@ namespace BizHawk.Emulation.Sound
short left = (short)((SectorCache[sectorOffset + 1] << 8) | (SectorCache[sectorOffset + 0])); short left = (short)((SectorCache[sectorOffset + 1] << 8) | (SectorCache[sectorOffset + 0]));
short right = (short)((SectorCache[sectorOffset + 3] << 8) | (SectorCache[sectorOffset + 2])); 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 * MaxVolume / short.MaxValue);
samples[offset++] += (short) (right * LogicalVolume / 100 * MaxVolume / short.MaxValue);
samples[offset++] += (short) (left * LogicalVolume / 100);
samples[offset++] += (short) (right * LogicalVolume / 100);
SectorOffset++; SectorOffset++;
if (SectorOffset == 588) if (SectorOffset == 588)
@ -207,7 +205,6 @@ namespace BizHawk.Emulation.Sound
{ {
get get
{ {
// TODO apply the damn volume
if (Mode != CDAudioMode.Playing) if (Mode != CDAudioMode.Playing)
return 0; return 0;

View File

@ -27,21 +27,23 @@ namespace BizHawk.Emulation.Sound
public PSGChannel[] Channels = new PSGChannel[8]; public PSGChannel[] Channels = new PSGChannel[8];
public byte VoiceLatch; public byte VoiceLatch;
private byte WaveTableWriteOffset; byte WaveTableWriteOffset;
private Queue<QueuedCommand> commands = new Queue<QueuedCommand>(256); Queue<QueuedCommand> commands = new Queue<QueuedCommand>(256);
private long frameStartTime, frameStopTime; long frameStartTime, frameStopTime;
private const int SampleRate = 44100; const int SampleRate = 44100;
private const int PsgBase = 3580000; 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 }; 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 }; static byte[] VolumeReductionTable = { 0x1F, 0x1D, 0x1B, 0x19, 0x17, 0x15, 0x13, 0x10, 0x0F, 0x0D, 0x0B, 0x09, 0x07, 0x05, 0x03, 0x00 };
public byte MainVolumeLeft; public byte MainVolumeLeft;
public byte MainVolumeRight; public byte MainVolumeRight;
public int MaxVolume { get; set; }
public HuC6280PSG() public HuC6280PSG()
{ {
MaxVolume = short.MaxValue;
Waves.InitWaves(); Waves.InitWaves();
for (int i=0; i<8; i++) for (int i=0; i<8; i++)
Channels[i] = new PSGChannel(); Channels[i] = new PSGChannel();
@ -129,7 +131,7 @@ namespace BizHawk.Emulation.Sound
} }
} }
public void DiscardSamples() { /*TBD*/ } public void DiscardSamples() { }
public void GetSamples(short[] samples) public void GetSamples(short[] samples)
{ {
int elapsedCycles = (int) (frameStopTime - frameStartTime); int elapsedCycles = (int) (frameStopTime - frameStartTime);
@ -193,8 +195,8 @@ namespace BizHawk.Emulation.Sound
channel.SampleOffset %= wave.Length; channel.SampleOffset %= wave.Length;
short value = channel.DDA ? channel.DDAValue : wave[(int) channel.SampleOffset]; short value = channel.DDA ? channel.DDAValue : wave[(int) channel.SampleOffset];
samples[i++] += (short)(value * LogScale[volumeLeft] / 255f / 6f); samples[i++] += (short)(value * LogScale[volumeLeft] / 255f / 6f * MaxVolume / short.MaxValue);
samples[i++] += (short)(value * LogScale[volumeRight] / 255f / 6f); samples[i++] += (short)(value * LogScale[volumeRight] / 255f / 6f * MaxVolume / short.MaxValue);
channel.SampleOffset += moveThroughWaveRate; channel.SampleOffset += moveThroughWaveRate;
channel.SampleOffset %= wave.Length; channel.SampleOffset %= wave.Length;

View File

@ -24,7 +24,7 @@ namespace BizHawk.Emulation.Sound
private const int SampleRate = 44100; 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 }; 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; if (Volume == 0) return;
@ -36,8 +36,8 @@ namespace BizHawk.Emulation.Sound
{ {
short value = Wave[(int)WaveOffset]; short value = Wave[(int)WaveOffset];
samples[i++] += (short)(Left ? (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] / 0x1FF) : 0); samples[i++] += (short)(Right ? (value / 4 * LogScale[Volume] / 0xFF * maxVolume / short.MaxValue) : 0);
WaveOffset += moveThroughWaveRate; WaveOffset += moveThroughWaveRate;
if (WaveOffset >= Wave.Length) if (WaveOffset >= Wave.Length)
WaveOffset %= Wave.Length; WaveOffset %= Wave.Length;
@ -55,6 +55,7 @@ namespace BizHawk.Emulation.Sound
public SN76489() public SN76489()
{ {
MaxVolume = short.MaxValue * 2 / 3;
Waves.InitWaves(); Waves.InitWaves();
for (int i=0; i<4; i++) for (int i=0; i<4; i++)
{ {
@ -416,7 +417,8 @@ namespace BizHawk.Emulation.Sound
#endregion #endregion
public void DiscardSamples() { /* todo */ } public int MaxVolume { get; set; }
public void DiscardSamples() { commands.Clear(); }
public void GetSamples(short[] samples) public void GetSamples(short[] samples)
{ {
int elapsedCycles = frameStopTime - frameStartTime; int elapsedCycles = frameStopTime - frameStartTime;
@ -435,7 +437,7 @@ namespace BizHawk.Emulation.Sound
public void GetSamplesImmediate(short[] samples, int start, int len) public void GetSamplesImmediate(short[] samples, int start, int len)
{ {
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
Channels[i].Mix(samples, start, len); Channels[i].Mix(samples, start, len, MaxVolume);
} }
class QueuedCommand class QueuedCommand

View File

@ -29,6 +29,8 @@ namespace BizHawk.Emulation.Sound
BaseSoundProvider.DiscardSamples(); BaseSoundProvider.DiscardSamples();
} }
public int MaxVolume { get; set; }
public void GetSamples(short[] samples) public void GetSamples(short[] samples)
{ {
int samplesToGenerate = SamplesInOneFrame; int samplesToGenerate = SamplesInOneFrame;

View File

@ -32,7 +32,9 @@ namespace BizHawk.Emulation.Sound
{ {
buffer.clear(); buffer.clear();
} }
}
public int MaxVolume { get; set; }
}
public interface ISynchronizingAudioBuffer public interface ISynchronizingAudioBuffer
{ {

View File

@ -3,7 +3,6 @@
namespace BizHawk.Emulation.Sound namespace BizHawk.Emulation.Sound
{ {
// This is a straightforward class to mix/chain multiple ISoundProvider sources. // 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 public sealed class SoundMixer : ISoundProvider
{ {
@ -35,5 +34,16 @@ namespace BizHawk.Emulation.Sound
foreach (var soundSource in SoundProviders) foreach (var soundSource in SoundProviders)
soundSource.GetSamples(samples); 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; }
} }
} }

View File

@ -18,6 +18,7 @@ namespace BizHawk.Emulation.Sound
public YM2413() public YM2413()
{ {
MaxVolume = short.MaxValue;
opll = OPLL_new(3579545, 44100); opll = OPLL_new(3579545, 44100);
} }
@ -39,11 +40,12 @@ namespace BizHawk.Emulation.Sound
} }
public void DiscardSamples() { } public void DiscardSamples() { }
public int MaxVolume { get; set; }
public void GetSamples(short[] samples) public void GetSamples(short[] samples)
{ {
for (int i=0; i<samples.Length;) for (int i=0; i<samples.Length;)
{ {
short val = calc(opll); short val = (short)(calc(opll) * MaxVolume / short.MaxValue);
samples[i++] = val; samples[i++] = val;
samples[i++] = val; samples[i++] = val;
} }

View File

@ -17,10 +17,8 @@
{ {
} }
public void DiscardSamples() { } public void DiscardSamples() {}
public void GetSamples(short[] samples) public void GetSamples(short[] samples) {}
{ public int MaxVolume { get; set; }
// TODO
}
} }
} }