DMG Sound Implementation - Kind of working but broken

Discussion of programming and development for the original Game Boy and Game Boy Color.
Post Reply
IndyBonez
Posts: 15
Joined: Sat Jan 13, 2018 5:16 am

DMG Sound Implementation - Kind of working but broken

Post by IndyBonez »

Hi Guys,

So over the Xmas period I took the plunge to try my hand at an emulator and like a lot of people chose the Gameboy as my first.

I have managed to implement quite an accurate emulator so far (built as a C# class library to run in many different engines like XNA, Monogame & Unity) and it passes more tests then a lot of other C# implementations out there. All the CPU, Memory and GPU side of things came easy to me and I had no real problems getting stuff going.

But audio has been a whole different ball game! I'm struggling to find documentation that goes into enough depth and I feel like I'm missing something! I spent about 2 weeks on the the rest of the emulator and have now spent about 3 week just researching and trying to get my head around the audio side of things. Other open source projects I have been looking at do things in wildly different ways and are never commented/documented well enough for me to fill in the gaps in my knowledge.

So I have done my best to get where I am now which is having a barely working Square Wave Generator for Channel 1. Compared to other emulators I seem to have got the timings correct but the sound coming out is just pops and cracks versus something correct. A saving grace I think is that the pops/cracks play at the right time so I can hear a resemblance to the correct tunes, so there must just be something wrong with how I'm converting the final bytes into the required audio formats (I'm hoping).

Eventually as mentioned I'm hoping to have this running in multiple game engines so I want my implementation to be generic enough that the sound output can be adapted to different sound libraries ie Unity sound or NAudio.

Any help trying to get me first to a working implementation with NAudio would be amazing and then I can try and adapt it for Unity later on.

Below are my WIP implementations of the APU and the SquareWaveGenerator plus info on how I'm interfacing with NAudio:

APU:

Code: Select all

using System;

namespace GBZEmuLibrary
{
    internal class APU
    {
        private const int FRAME_SEQUENCER_UPDATE_THRESHOLD = Sound.SAMPLE_RATE / APUSchema.FRAME_SEQUENCER_RATE;

        private readonly byte[] _memory = new byte[MemorySchema.APU_REGISTERS_END - MemorySchema.APU_REGISTERS_START];

        private readonly SquareWaveGenerator _channel1;

        private bool _powered = true;

        private readonly int _maxCyclesPerSample;
        private int _cycleCounter;

        private int _frameSequenceTimer;

        private byte[] _buffer = new byte[(Sound.SAMPLE_RATE / GameBoySchema.TARGET_FRAMERATE) * 2];

        private int _currentByte = 0;

        public APU()
        {
            _maxCyclesPerSample = GameBoySchema.MAX_DMG_CLOCK_CYCLES / Sound.SAMPLE_RATE;

            _channel1 = new SquareWaveGenerator();
        }

        public byte[] GetSoundSamples()
        {
            //TODO may need to reset buffer
            _currentByte = 0;
            return _buffer;
        }

        public void Reset()
        {
            WriteByte(0x80, 0xFF10);
            WriteByte(0xBF, 0xFF11);
            WriteByte(0xF3, 0xFF12);
            WriteByte(0xBF, 0xFF14);
            WriteByte(0x3F, 0xFF16);
            WriteByte(0x00, 0xFF17);
            WriteByte(0xBF, 0xFF19);
            WriteByte(0x7F, 0xFF1A);
            WriteByte(0xFF, 0xFF1B);
            WriteByte(0x9F, 0xFF1C);
            WriteByte(0xBF, 0xFF1E);
            WriteByte(0xFF, 0xFF20);
            WriteByte(0x00, 0xFF21);
            WriteByte(0x00, 0xFF22);
            WriteByte(0xBF, 0xFF23);
            WriteByte(0x77, 0xFF24);
            WriteByte(0xF3, 0xFF25);
            WriteByte(0xF1, 0xFF26);
        }

        public void WriteByte(byte data, int address)
        {
            int freqLowerBits, freqHighBits;

            switch (address)
            {
                case APUSchema.SQUARE_1_SWEEP_PERIOD:
                    // Register Format -PPP NSSS Sweep period, negate, shift
                    _channel1.SetSweep(data);
                    break;
                case APUSchema.SQUARE_1_DUTY_LENGTH_LOAD:
                    // Register Format DDLL LLLL Duty, Length load (64-L)
                    _channel1.SetLength(data);
                    _channel1.SetDutyCycle(data);
                    break;
                case APUSchema.SQUARE_1_VOLUME_ENVELOPE:
                    // Register Format VVVV APPP Starting volume, Envelope add mode, period
                    _channel1.SetEnvelope(data);
                    break;
                case APUSchema.SQUARE_1_FREQUENCY_LSB:
                    // Register Format FFFF FFFF Frequency LSB

                    freqLowerBits = data;
                    freqHighBits = Helpers.GetBits(ReadByte(APUSchema.SQUARE_1_FREQUENCY_MSB), 3) << 8;

                    _channel1.SetFrequency(freqHighBits + freqLowerBits);
                    break;
                case APUSchema.SQUARE_1_FREQUENCY_MSB:
                    // Register Format TL-- -FFF Trigger, Length enable, Frequency MSB

                    freqLowerBits = ReadByte(APUSchema.SQUARE_1_FREQUENCY_LSB);
                    freqHighBits  = Helpers.GetBits(data, 3) << 8;

                    _channel1.SetFrequency(freqHighBits + freqLowerBits);

                    if (!Helpers.TestBit(data, 6))
                    {
                        _channel1.SetLength(0);
                    }

                    //Trigger Enabled
                    if (Helpers.TestBit(data, 7))
                    {
                        _channel1.Inited = true;

                        //TODO handle trigger
                        if (_channel1.Length == 0)
                        {
                            _channel1.SetLength(64);
                        }

                        _channel1.SetVolume(_channel1.InitialVolume);
                    }
                    break;

                case APUSchema.SQUARE_2_DUTY_LENGTH_LOAD:
                    break;
                case APUSchema.SQUARE_2_VOLUME_ENVELOPE:
                    break;
                case APUSchema.SQUARE_2_FREQUENCY_LSB:
                    break;
                case APUSchema.SQUARE_2_FREQUENCY_MSB:
                    break;

                case APUSchema.VIN_VOL_CONTROL:
                    // Register Format ALLL BRRR Vin L enable, Left vol, Vin R enable, Right vol

                    break;

                case APUSchema.STEREO_SELECT:
                    // Register Format 8 bits 
                    // Lower 4 bits represent Right Channel for Channels 1-4
                    // Higher 4 bits represent Left Channel for Channels 1-4
                    StereoSelect(data);
                    break;
                case APUSchema.SOUND_ENABLED:
                    HandlePowerToggle(Helpers.TestBit(data, 7));
                    break;
    }

            _memory[address - MemorySchema.APU_REGISTERS_START] = data;
        }

        public byte ReadByte(int address)
        {
            // TODO NRx3 & NRx4 return 0 upon reading
            return _memory[address - MemorySchema.APU_REGISTERS_START];
        }

        public void Update(int cycles)
        {
            if (!_powered)
            {
                return;
            }

            _cycleCounter += cycles;

            //Check if ready to get sample
            if (_cycleCounter < _maxCyclesPerSample)
            {
                return;
            }

            _cycleCounter -= _maxCyclesPerSample;

            _frameSequenceTimer++;

            if (_frameSequenceTimer >= FRAME_SEQUENCER_UPDATE_THRESHOLD)
            {
                _channel1.Update();
            }

            byte leftChannel = 0;
            byte rightChannel = 0;

            if (_channel1.Enabled)
            {
                var sample = _channel1.GetCurrentSample();

                if ((_channel1.ChannelState & APUSchema.CHANNEL_LEFT) != 0)
                {
                    leftChannel += sample;
                }

                if ((_channel1.ChannelState & APUSchema.CHANNEL_RIGHT) != 0)
                {
                    rightChannel += sample;
                }
            }

            //TODO need to determine best way to handle overflow
            if (_currentByte * 2 < _buffer.Length - 1)
            {
                _buffer[_currentByte * 2]     = (byte)(leftChannel);
                _buffer[_currentByte * 2 + 1] = (byte)(rightChannel);

                _currentByte++;
            }
        }

        private void StereoSelect(byte val)
        {
            _channel1.ChannelState = GetChannelState(val, 1);
        }

        private int GetChannelState(byte val, int channel)
        {
            var channelState = 0;

            // Testing bits 0-3 
            if (Helpers.TestBit(val, channel - 1)) 
            {
                channelState |= APUSchema.CHANNEL_RIGHT;
            }

            // Testing bits 4-7
            if (Helpers.TestBit(val, channel + 3))
            {
                channelState |= APUSchema.CHANNEL_LEFT;
            }

            return channelState;
        }

        private void HandlePowerToggle(bool newState)
        {
            if (!newState && _powered)
            {
                //Reset registers (except length counters on DMG)
            }
            else if (newState && !_powered)
            {
                //Reset frame sequencer
            }
        }
    }
}
SquareWaveGenerator:

Code: Select all

using System;

namespace GBZEmuLibrary
{
    // Ref 1 - https://emu-docs.org/Game%20Boy/gb_sound.txt

    internal class SquareWaveGenerator : IGenerator
    {
        private const int MAX_11_BIT_VALUE = 2048; //2^11
        private const int MAX_4_BIT_VALUE = 16; //2^4

        public int Length => _totalLength;
        public int InitialVolume => _initialVolume;

        public bool Inited { get; set; }
        public bool Enabled => _totalLength > 0 && Inited;
        public int ChannelState { get; set; }

        private int _initialSweepPeriod;
        private int _sweepPeriod;
        private int _shiftSweep;
        private bool _negateSweep;

        private int _totalLength;

        private float _dutyCycle;
        private bool _dutyState;

        private int _initialVolume;
        private int _volume;
        private int _envelopePeriod;
        private int _initialEnvelopePeriod;
        private bool _addEnvelope;

        private int _originalFrequency;
        private int _frequency;
        private int _frequencyCount;

        private int _sequenceTimer;

        public void Update()
        {
            //256Hz
            if (_sequenceTimer % 2 == 0)
            {
                _totalLength = Math.Max(0, _totalLength - 1);
            }

            //128Hz
            if ((_sequenceTimer + 2) % 4 == 0)
            {
                _sweepPeriod--;

                if (_shiftSweep != 0 && _sweepPeriod == 0)
                {
                    _sweepPeriod = _initialSweepPeriod;

                    var sweepFreq = _originalFrequency + (_negateSweep ? -1 : 1) * (_originalFrequency >> _shiftSweep);

                    if (sweepFreq >= MAX_11_BIT_VALUE)
                    {
                        //TODO may need an actual enabled flag
                        _totalLength = 0;
                    }
                    else if (sweepFreq > 0)
                    {
                        SetFrequency(sweepFreq);
                    }
                }
            }

            //64Hz
            if (_sequenceTimer % 7 == 0)
            {
                _envelopePeriod--;

                if (_envelopePeriod == 0)
                {
                    _envelopePeriod = _initialEnvelopePeriod;
                    _volume += _addEnvelope ? 1 : -1;
                    _volume = Math.Max(_volume, 0);
                    _volume = Math.Min(_volume, MAX_4_BIT_VALUE - 1);
                }
            }

            _sequenceTimer = (_sequenceTimer + 1) % 8;
        }

        public byte GetCurrentSample()
        {
            byte sample = 0;

            _frequencyCount++;

            if (_frequencyCount > _frequency * (_dutyState ? _dutyCycle : 1 - _dutyCycle))
            {
                _frequencyCount = 0;

                sample = (byte)(_dutyState ? _volume : -_volume);

                _dutyState = !_dutyState;
            }

            return sample;
        }

        public void SetSweep(byte data)
        {
            // Val Format -PPP NSSS
            _shiftSweep = Helpers.GetBitsIsolated(data, 0, 3);

            _negateSweep = Helpers.TestBit(data, 4);

            _initialSweepPeriod = Helpers.GetBitsIsolated(data, 4, 3);
            _sweepPeriod = _initialSweepPeriod;
        }

        public void SetLength(byte data)
        {
            // Val Format --LL LLLL
            _totalLength = 64 - Helpers.GetBits(data, 6);
        }

        public void SetLength(int length)
        {
            _totalLength = length;
        }

        public void SetDutyCycle(byte data)
        {
            // Val Format DD-- ----
            _dutyCycle = Helpers.GetBitsIsolated(data, 6, 2) * 0.25f;
            _dutyCycle = Math.Max(0.125f, _dutyCycle);
        }

        public void SetEnvelope(byte data)
        {
            // Val Format VVVV APPP
            _initialEnvelopePeriod = Helpers.GetBits(data, 3);
            _envelopePeriod = _initialEnvelopePeriod;

            _addEnvelope = Helpers.TestBit(data, 3);

            _initialVolume = Helpers.GetBitsIsolated(data, 4, 4);
            SetVolume(_initialVolume);
        }

        public void SetVolume(int volume)
        {
            _volume = volume;
        }

        public void SetFrequency(int freq)
        {
            _originalFrequency = freq;
            _frequency = Sound.SAMPLE_RATE / (GameBoySchema.MAX_DMG_CLOCK_CYCLES / ((MAX_11_BIT_VALUE - (freq % MAX_11_BIT_VALUE)) << 5));
        }
    }
}
NAudio Integration:

Code: Select all

_bufferedWaveProvider = new BufferedWaveProvider(new WaveFormat(Sound.SAMPLE_RATE, 16, 1));

_waveOut = new WaveOut();
_waveOut.Init(_bufferedWaveProvider);
_waveOut.Play(); //TODO sound may need to be delayed
So basically I call GetSoundSamples() at 60 FPS which returns a byte array that I pass to NAudio like so:

Code: Select all

var buffer = _emulator.GetSoundSamples();

_bufferedWaveProvider.AddSamples(buffer, 0, buffer.Length);
Constants:

Code: Select all

public class Sound
{
    public const int SAMPLE_RATE = 44100;
}

internal class APUSchema
{
    public const int CHANNEL_LEFT  = 1;
    public const int CHANNEL_RIGHT = 2;
    public const int CHANNEL_MONO  = 4;

    public const int FRAME_SEQUENCER_RATE = 512;
    public const int LENGTH_RATE          = 256;

    public const int SQUARE_1_SWEEP_PERIOD     = 0xFF10;
    public const int SQUARE_1_DUTY_LENGTH_LOAD = 0xFF11;
    public const int SQUARE_1_VOLUME_ENVELOPE  = 0xFF12;
    public const int SQUARE_1_FREQUENCY_LSB    = 0xFF13;
    public const int SQUARE_1_FREQUENCY_MSB    = 0xFF14;

    public const int SQUARE_2_DUTY_LENGTH_LOAD = 0xFF16;
    public const int SQUARE_2_VOLUME_ENVELOPE  = 0xFF17;
    public const int SQUARE_2_FREQUENCY_LSB    = 0xFF18;
    public const int SQUARE_2_FREQUENCY_MSB    = 0xFF19;

    public const int VIN_VOL_CONTROL = 0xFF24;
    public const int STEREO_SELECT   = 0xFF25;
    public const int SOUND_ENABLED   = 0xFF26;
}
The APU gets updated about every 4 CPU ticks along with the GPU, timer etc

As part of my implementation I'm trying to make the code as readable as possible so other can follow along after me as well, so If you need the values of any of the constants that I haven't provided just let me know

I look forward to hearing you guys thoughts
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: DMG Sound Implementation - Kind of working but broken

Post by lidnariq »

IndyBonez wrote:

Code: Select all

        public byte[] GetSoundSamples()        {
            //TODO may need to reset buffer
            _currentByte = 0;
            return _buffer;
        }
[...]
            //TODO need to determine best way to handle overflow
By having separate methods for "calculate samples" and "fetch samples", you're inviting stuttering and other problems with desynchronization. Instead, just have a single method that calculates the buffer and returns it; then you can't overflow.

Code: Select all

            //Check if ready to get sample
            if (_cycleCounter < _maxCyclesPerSample)
            {
                return;
            }
[...]
                var sample = _channel1.GetCurrentSample();
Here's your problem: you only update the status of your output channels once when it's time to emit a new sample, but the GetCurrentSample method itself expects to be called for every CPU cycle.
IndyBonez
Posts: 15
Joined: Sat Jan 13, 2018 5:16 am

Re: DMG Sound Implementation - Kind of working but broken

Post by IndyBonez »

Hi lidnariq,

Thank you very much for the fast reply! Forgive me if I'm asking a lot of questions just trying to get my head around things

So my understanding of how the sound libraries are expecting data is that they are expecting 44100 (as an example of the rate mine is running at the moment) samples a second. My main loop runs at 60FPS and every frame it requests new data to feed to the waveout. Which would mean 44100 / 60 samples every frame. Any more then that and I'm not sure how the sound library would react? So that is what I'm attempting to do here generate as many samples as the library can handle in sync with the CPU. I have seen implementations as you suggested where it only generates the buffer when requested but I don't quite understand how this stays in sync with the CPU if its not being timed off that?

Anyway in terms of the actual issue just so I have it clear in my head, when you talk about GetCurrentSample being called every CPU cycles do you mean I shouldn't be calling it 44100 times a second? And instead be calling it 4194304 times a second? Wouldn't that generate quite a number of samples? And I guess to support that I need to change how I time my frequency in the channel?

Thanks for the help its very much appreciated!
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: DMG Sound Implementation - Kind of working but broken

Post by lidnariq »

IndyBonez wrote:Which would mean 44100 / 60 samples every frame. Any more then that and I'm not sure how the sound library would react? So that is what I'm attempting to do here generate as many samples as the library can handle in sync with the CPU. I have seen implementations as you suggested where it only generates the buffer when requested but I don't quite understand how this stays in sync with the CPU if its not being timed off that?
It's messy, as you're guessing.

In the original DMG, there's a single clock source, at 2²² Hz, and everything operates off that. Sound is generated as sample rates per channel ranging from 2²¹ Hz (channel 3 / wave) to 2¹⁹ (channel 4 / noise). The frame rate is exactly 2²² Hz ÷ 456 ÷ 154 ≈ 59.7275... Hz.

In contrast, the PC probably has separate clock sources for audio and video (exception: using same HDMI channel for audio and video). These will differ from each other by a small amount—probably a few hundred ppm—and any system will either have to deal with inaccurate emulation, audio dropout, latency, detuned audio, skipped video frames, duplicated video frames, tearing, or some combination of all of the above.

We've had people in the forum here write NES emulators that are video-locked (and audio just suffers for it), audio-locked (and video suffers), and one person who's written an emulator that dynamically adjusts the audio resampling method to keep the two matching.
Anyway in terms of the actual issue just so I have it clear in my head, when you talk about GetCurrentSample being called every CPU cycles do you mean I shouldn't be calling it 44100 times a second? And instead be calling it 4194304 times a second? Wouldn't that generate quite a number of samples? And I guess to support that I need to change how I time my frequency in the channel?
It looks to me like that implementation of GetCurrentSample, only for channel 1, expects to be called at 2²⁰ Hz. You'll note that it's not GetCurrentSample that enqueues the result of its arithmetic; you could just get away with calling it N times during each update. (Where N oscillates between two different numbers)
IndyBonez
Posts: 15
Joined: Sat Jan 13, 2018 5:16 am

Re: DMG Sound Implementation - Kind of working but broken

Post by IndyBonez »

Thank you again for the reply.

I tried what you suggested but there was no noticeable change unfortunately, though while looking at GetCurrentSample I noticed a mistake in that I was only every returning data when the duty changed and at no other time so that meant that sounds were playing for a lot less time then they should of. I fixed it with this change

Code: Select all

        public byte GetCurrentSample()
        {
            _frequencyCount++;

            if (_frequencyCount > _frequency * (_dutyState ? _dutyCycle : 1 - _dutyCycle))
            {
                _frequencyCount = 0;
                _dutyState = !_dutyState;
            }

            return (byte)(_dutyState ? _volume : -_volume);
        }
This has resulted in fuller and louder sounds but they still seemed to be poppy/crackly in nature, I have attached a WAV file with the result in the hopes it might help point us in the right direction
Attachments
Tetris.mp3
Poppy audio from Tetris
(496.2 KiB) Downloaded 258 times
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: DMG Sound Implementation - Kind of working but broken

Post by lidnariq »

MP3 is really bad for this kind of analysis, because it destroys phase information and can cause erroneous echo / preecho.

But my best guess given the recording is that something is causing the waveform to play through exactly twice (one positive and one negative edge) and then isn't resetting phase.
IndyBonez
Posts: 15
Joined: Sat Jan 13, 2018 5:16 am

Re: DMG Sound Implementation - Kind of working but broken

Post by IndyBonez »

I do have a WAV file version of it but the forum wouldn't let me upload it maybe in a zip? If having that would be useful?

Anyway I have found a similar implementation to mine using the same audio library NAudio and its is pretty much using the library the same way as me but it doesn't have any problems with the audio except for stuttering etc, so I think there is a problem in the emulation of the sound channels somewhere I just can't seem to pinpoint it. Here is the other implementation for reference https://github.com/CidVonHighwind/GameB ... o/Sound.cs.

I may have to resort to maybe dumping the buffer from that and comparing the output, though I'm hoping a second set of eyes can help my get to the bottom of the issue
MegaBoyEXE
Posts: 12
Joined: Tue Jul 04, 2017 9:22 am

Re: DMG Sound Implementation - Kind of working but broken

Post by MegaBoyEXE »

I'm implementing my emulator in C# too, currently inside Unity3D, but the core code is separated from Unity3D dependency and can converted to a lib.
I'm trying to document most of my findings on APU and all channels, and I can now pass almost all tests from dmg_sound tests, missing a few details with sweep tests and the wave pattern r/w tests.

I'm working with audio in a separate branch while I do tests, but here they are
The APU code: https://github.com/fattard/xFF/blob/gb/ ... /HW/APU.cs
And channels: https://github.com/fattard/xFF/tree/gb/ ... B/HW/audio
Last edited by MegaBoyEXE on Sun Jan 14, 2018 1:32 pm, edited 2 times in total.
IndyBonez
Posts: 15
Joined: Sat Jan 13, 2018 5:16 am

Re: DMG Sound Implementation - Kind of working but broken

Post by IndyBonez »

MegaBoyEXE wrote:I'm working with audio in a separate branch while I do tests, but here they are
Great thank you very much for this reference I will see if I can try and track down any discrepancies in my own code
MegaBoyEXE
Posts: 12
Joined: Tue Jul 04, 2017 9:22 am

Re: DMG Sound Implementation - Kind of working but broken

Post by MegaBoyEXE »

The APU has too many quirks that are hard to follow and implement.
I would recommend you to start with a simpler channel, like channel 2 or 3.
Channel 3 is quite simple, just update pos in waveform table and outputs the shifted volume.
Channel 2 is also simple, just need volume envelope implementation.
Channel 1 is the same as channel 2, but the sweep operations have many exceptions you need to follow carefully.
I've started with channel 3, than went to 2, then 1, and finally 4.

As lidnariq mentioned, the problem is your update method.
You need to update APU internals the same way you do with Timer, Video, etc: process at every elapsed cycle (or multiples of 4, depends on you using Machine-Cycle or clock-Cycles).
The different concept that enters here is that you just don't output sound every cycle. Instead, you have your sampling rate of (4194304 / 44100) cycles.
At each ~95 cycles, you output 1 sample, but you need to update all channels each cycle. Think this as frame skip: you process rendering routine EVERY cycle, but just draw to screen at every few cycles, skipping some.

Then comes the output buffer concept.
I have seen 2 ways so far, depending on the audio lib

1- You are responsible to feed the audio buffer: SDL works this way. You fill a buffer of 44100/60 samples per frame, and sends to the audio lib when it's filled. Some people fills like 1024 samples. This is adjustable, depends on how much process time you have. From the code above, at each 95 cycles, you fill 1 sample in the buffer, so you need to fill all the remaining samples before sending to audio lib;
2- The audio lib requests when it needs more samples. Unity3D works this way. You will still be filling a buffer if 44100/60 samples per frame, but another thread will call a callback on your code where you pass your filled buffer). You will need to have this ready for the time it's requested, otherwise gaps will occur.

I was having stuttering too, but I calculated how many samples I was generating in my APU per frame (~735), and the requested buffer was 1024. I changed the audio lib config to request a buffer of 735 instead of 1024, so I just do a copy of my internal buffer to the audio buffer, and stuttering stopped.

Those are my tips for now.
IndyBonez
Posts: 15
Joined: Sat Jan 13, 2018 5:16 am

Re: DMG Sound Implementation - Kind of working but broken

Post by IndyBonez »

Cool thanks for the advice. I haven't had a chance to look much into it yet though I updated my timing methods as what you suggested made a lot of sense updating most things off the clock cycles, that alone didn't help so i still suspect something else is wrong. I'll get to the bottom of it eventually I imagine. Though if anyone manages to figure out what else might be the issue in the posted code I would love to hear your thoughts otherwise I'll update here once I have found a solution so other intrepid explorers can use my findings as a learning.
IndyBonez
Posts: 15
Joined: Sat Jan 13, 2018 5:16 am

Re: DMG Sound Implementation - Kind of working but broken

Post by IndyBonez »

So I managed to get channel 1 to work yay! It was a silly I had done in the end where I wasn't resetting the frame sequence counter when it went over the threshold!

So now I'm moving onto Channel 2 which is also a SquareWaveGenerator minus the sweep and it doesn't work at all! And its using the exact same class :( is there anything I need to be aware of with channel 2? As long as I ensure the sweep isn't enabled it should just be the exact same right?
IndyBonez
Posts: 15
Joined: Sat Jan 13, 2018 5:16 am

Re: DMG Sound Implementation - Kind of working but broken

Post by IndyBonez »

Okay actually its something to do with have I'm setting the left/right channel state for the second channel, prob just my maths wrong
IndyBonez
Posts: 15
Joined: Sat Jan 13, 2018 5:16 am

Re: DMG Sound Implementation - Kind of working but broken

Post by IndyBonez »

Nope tracked it down to NAudio only playing sound from the right channel and not the left! Anyone here have experience with NAudio and can maybe help me figure out what I'm doing wrong with setting out the left/right channels?
Post Reply