rsta2 / circle

A C++ bare metal environment for Raspberry Pi with USB (32 and 64 bit)
https://circle-rpi.readthedocs.io
GNU General Public License v3.0
1.86k stars 249 forks source link

PWMsoundbasedevice stereo output #65

Closed EvilDraggie closed 6 years ago

EvilDraggie commented 6 years ago

I've been trying to implement stereo output on my synthesizer, having already succesfully gotten it working in mono, however I keep getting a rather "bitcrushed" audio output as soon as the left and right samples differ.

I.e. I have two unsigned integer samples, which are both between 0 and the maximum PWM value using GetRange(), one for the left channel and one for the right. If the synth runs in mono these two samples have the same value (i.e. channelleft= sampleleft and channelright = sampleleft too) and the audio output on both channels is fine. However as soon as I make things stereo (i.e. channelleft= sampleleft and channelright = sampleright) the sound becomes horrible. I've already tested both the left and right samples in mono and both generate correct audio, so I'm certain it's not that.

I've also looked at the samples and your MiniSynth project and it looks like I'm doing things correctly (though all examples are mono :) ).

Any pointers on what could cause this?

rsta2 commented 6 years ago

Because the samples are all mono I did two tests again:

Both tests are based on the class CPWMSoundBaseDevice and worked well. Currently I have unfortunately no idea why this is not working with your project.

EvilDraggie commented 6 years ago

I've finally been able to do some further testing: I've set the RPI to generate a sine wave on the left and right channel (2 separate oscillators) and have done different tests with different phase differences between the two sine waves (see picture): rpizerostereopwmissue

As you can see noise and distortion of the waveforms appear as soon as they are out of phase i.e. the oscillators produce different samples.

Below is my final output code, i've modified it to be pretty much a stereo version of your output code for safe measure. 'mCurrSynth' is a pointer to the current synthesizer object, I've set my synth up so that it has multiple kinds of synthesizers loaded in memory (FM synth, stringsynth, SH101 emulation, etc) and so I can switch between them. The 'Step' method of the synthesizer object sets the referenced variables (sigL, sigR) to the current sample values generated by the sine wave code.

`unsigned GetChunk(u32 *pBuffer, unsigned nChunkSize) { unsigned nResult = nChunkSize;

    for (; nChunkSize > 0; nChunkSize -= 2)
    {           
        float sigL = 0.f;
        float sigR = 0.f;

        mCurrSynth->Step(sigL, sigR);

        int nLevelL = (int)(sigL*m_nVolumeLevel + m_nNullLevel);

        if (nLevelL > (int) m_nMaxLevel)
        {
            nLevelL = m_nMaxLevel;
        }
        else if (nLevelL < 0)
        {
            nLevelL = 0;
        }

        int nLevelR = (int)(sigR*m_nVolumeLevel + m_nNullLevel);

        if (nLevelR > (int) m_nMaxLevel)
        {
            nLevelR = m_nMaxLevel;
        }
        else if (nLevelR < 0)
        {
            nLevelR = 0;
        }

        *pBuffer++ = (unsigned)nLevelL;
        *pBuffer++ = (unsigned)nLevelR;
    }

    return nResult;
}`
rsta2 commented 6 years ago

OK, the out-of-phase waves looks weird. Your code seems to be all right. I will try to reproduce this here with a little program with two oscillators (left / right). Not sure if I can see this with my relatively cheap oscilloscope, but I will see. Perhaps I can hear the distortion.

EvilDraggie commented 6 years ago

The distortion/noise is very much audible. I also noticed that when the right channel only produces silence (i.e. it's set at the m_nNullLevel value) I can faintly hear the left channel's output, though this -might- just be my audio interface.

Thanks for your help :)

rsta2 commented 6 years ago

I have written a small sample program, which outputs two different waves to the left and right PWM channel. You find the source code attached to this comment. The waves can be different in wave form, frequency and phase. This has to be configured in CMiniOrgan::Initialize() in miniorgan.cpp. The sample rate and volume can be configured in config.h.

When I let this program run on a Raspberry Pi 3 I can hear a clear 440 Hz tone on both channels, although there is a 90 degrees phase difference between them. The following wave image has been captured using Audacity. As you can see, the phase difference can be seen and the waves are not distorted. I also tried two different frequencies (440 and 880 Hz) on the two channels. This sounds all right too.

wave

Can you please try this sample program on your hardware. Which Raspberry Pi model do you use? Do you have some additional hardware connected, which may cause the distortion? Perhaps you can find the reason for the problem experimenting with this program. Unfortunately I cannot reproduce the problem here.

pwmstereotest.zip

EvilDraggie commented 6 years ago

Thanks, I'll run the test program on the RPI Zero I'm using for this project, there's definitely something wierd going on, hopefully I can find it this way.

There's only two CD4022 counters for scanning the keyboard and frontpanel matrixes connected to the RPI, but if those cause noise I'd expect this to show up in the waveform, regardless of phase.

EvilDraggie commented 6 years ago

Well, seems to be an hardware issue somehow, I seem to be getting the same issues with your test program :(

It's not coming from the matrix scanners though, as those do nothing with your code and there's still the oddity of the waveforms being clean when in phase.

I'm going to keep digging.

EvilDraggie commented 6 years ago

Wrong button oops :)

Here's the waveform from the test, I again also varied the phase between tests (by giving the two oscillators different frequencies): rpizeropwmtest2

jsanchezv commented 6 years ago

Could be a problem with power supply?. A bad or very cheap power adapter can induce many noise in the circuits. Can you test with another power supply?.

It's a known problem, indeed.

rsta2 commented 6 years ago

If you are using a RPi Zero, you need some external circuit between the GPIO header and the headphone, because the RPi Zero does not have a headphone jack. Are you sure this circuit is all right? You wrote, you can hear the other channel when one channel is silent. This sounds to me that there is some interference between the channels.

Unfortunately I do not have such adapter circuit, so I cannot test this on my RPi Zero. I'm also not an electronics engineer, so it's difficult for me to debug hardware issues. At the moment it looks to me, this problem is of that kind.

EvilDraggie commented 6 years ago

I'm gonna try out some other PSU's. Perhaps it does require more filtering. I know in my other (Arduino powered) synth I had to create some very extensive filtering, using chokes and caps, to get clean audio but this was due to the Arduino's being noisy. From what I understood the RPI Zero -should- already have a proper clean 3.3V rail and I'm using a 7805 regulator with proper filter caps to feed the RPI, so it -should- be good?

The circuit attached to the outputs is a very standard RC low pass filter with the cutoff frequency at around 16KHz, on each channel. To be sure I've also tested without the filters and the results are the same (though very slightly more noisy of course). I'm also not using headphones, the signal is going into an audio interface as this has an high input impedance and thus will not load the RC filter when testing.

rsta2 commented 6 years ago

To find out if it is a hardware or software problem you could use Sonic Pi on Raspbian Stretch to generate some sound over PWM. On the RPi Zero you have to enter the following commands after boot to direct the PWM audio to GPIO12 and GPIO13 like Circle is doing it:

sudo raspi-gpio set 12 a0
sudo raspi-gpio set 13 a0

This sets both GPIOs to Alternate Function 0, which is PWM for these pins.

Then you can start Sonic Pi and enter the following program:

use_synth :sine

play :a4, pan: -1, release: 10
play :a5, pan: 1, release: 10

This plays a sine wave for note A4 (440 Hz) on the left side (pan -1) for 10 seconds and for note A5 (880 Hz) on the right side (pan 1). The volume is continuously reduced while playing.

If the sound is OK here, it is a Circle problem, otherwise a hardware problem.

EvilDraggie commented 6 years ago

Bit of a problem: I don't have an HDMI screen or keyboard peripheral for the RPI, so there isn't much I can do unless I can set it up on the SD card beforehand.

It's an idea to do something like that though, I'll look for alternatives. Thanks again :)