stardot / b-em

An opensource BBC Micro emulator for Win32 and Linux
http://stardot.org.uk/forums/viewtopic.php?f=4&t=10823
GNU General Public License v2.0
118 stars 58 forks source link

Music 5000 audio output sounds pretty rough #11

Closed hoglet67 closed 7 years ago

hoglet67 commented 7 years ago

These comments apply to official B-Em 2.2 release, as well as the current build. The problem seems to be present on Linux and Windows.

The changes I have made so far in db-music5000 haven't made things worse, but also haven't made things better. But then I wouldn't have expected them to change much (it's mostly been refactoring)

In very general terms, the audio output sounds pretty rough. I really noticed this when I hooked up a decent amp and speakers, so that I could compare the B-Em Music 5000 emulation with an FPGA hardware implementation (that uses a decent SPI DAC). The difference was very marked!

Initially I assumed it was something amiss with the Music 5000 emulation, but then I realised that even the ^G Beep (VDU 7) was suffering.

I think there are several things going on:

Something is definitely not right here.

As a comparison, I tried the Beech emulator on Windows, and this sounded very clean in comparison.

I need to do some more investigation, but I would be interested in feedback from other people. Most people I suspect would not notice, unless the issue is pointed out to them.

SteveFosdick commented 7 years ago

I will see if I can try listening to something. I think you did mention in one of the threads on the board that OpenAL was doing re-sampling by linear interpolation and I don't think I have ever come across and application in which that produces acceptable results. Have you changed that? Can you? I have done nothing with OpenAL before.

hoglet67 commented 7 years ago

I haven't tried to change the interpolation, and it's not obvious how to do that.

I tried one experiment earlier: hack sound.c to fill each audio buffer with a simple square wave. Each audio buffer is 2000 samples, and the sample rate is 31.25KHz, so a buffer lasts 32ms. I filled each new audio buffer with a period-40 square wave. The result sounded much cleaner than the ^G Beep.

The next thing I was to try is a simple square wave whose period isn't a factor of 2000. This should let us hear if buffers are being dropped.

There are probably several issues here, but it is possible there is a bug in the sn76489 emulation.

SteveFosdick commented 7 years ago

Ok, so doing something really basic, i.e. SOUND 1,-15,128,20 yes it is very quiet. I do have a HiFi amp connected, though, so I can turn the gain up quite a bit and I am not aware of any noise when B-Em is not making any sound.

I don't have the real BBC do hand so do you know what waveshape the sound command is supposed to produce? If it is supposed to be a sine wave it most definitely is not, it's much too bright.

Thinking on your squarewave test, something adding distortion will be more obvious if the starting point is a sine wave because then any extra harmonics have to have been added in the processing chain.

SteveFosdick commented 7 years ago

Another thought - if you suspect more than one issue then it may be better to work backwards, i.e. the closest we get to the PC's sound hardware from within B-Em. Presumably that is the OpenAL API? If that doesn't sound clean then we know it is OpenAL or the options we have setup OpenAL with.

hoglet67 commented 7 years ago

@SteveFosdick: sn76489 I think only produces square waves, but there is some low pass filtering in the Beeb's audio amplifier that means higher harmonics get attenuated.

Try the following: FOR A=10 TO 250 STEP 10 SOUND 1,-15,A,50 NEXT

For me anyway, some tones sound clean, others don't. And the higher ones are worse.

@SteveFosdick: yes, that's broadly what I'm trying to do.

The audio output is quite low level and has some HF noise. But there is something else going on in addition to this. It might be resampling artifacts. It might be lost buffers. It might be a bug in the sn76489. More debugging is needed.

SteveFosdick commented 7 years ago

Ok, I tried that. I think what I am hearing is the intended tone beating with a sampling clock which is a fixed frequency and thus depending on the sound being made the beat frequencies are different.

SteveFosdick commented 7 years ago

So, if I replace the sound_poll function in sound.c with this:

void sound_poll()
{
    double x = sin((2*M_PI)/(BUFLEN>>1)*sound_pos*100) * 32767;
        sound_buffer[sound_pos++] = (int)x;
        sound_buffer[sound_pos++] = (int)x;
        if (sound_pos >= BUFLEN) {
        al_givebuffer(sound_buffer);
                sound_pos = 0;
        }
}

It very definitely gives two tones mixed.

hoglet67 commented 7 years ago

What fundamental frequency were you aiming at above? It looks quite high. My calculation puts it at repeating every 10 samples which would be 3.125HKz. Does that sound about right?

Note, the sound buffer is actually mono at this stage. Pairs of adjacent samples being identical will give some weird harmonics. Try calculating x twice!

The original code is quite confusing, but poll_sound() is called every 64 us, and needs to produce 2 audio samples, that are eventually played back at 31.25KHz (i.e. every 32us)

SteveFosdick commented 7 years ago

If the sound buffer is mono then the loop that copies into what is presumably a stereo interleaved buffer in soundopenal.c, i.e. the al_givebuffer function is doing something odd:

for (c = 0; c < (BUFLEN >> 1); c++) zbuf[c] = buf[c >> 1];

So for half the length of the buffer from sound.c, copy each sample into adjacent positions in the new buffer that is about to be handed to OpenAL. Should that be:

for (c = 0; c < (BUFLEN << 1); c++) zbuf[c] = buf[c >> 1];

i.e. with the shift on BUFLEN the other way? If we are doing mono to stereo the new buffer (zbuf) should have twice as many samples as the old (buf) and c appears to be indexing the new buffer (zbuf).

If use a sine wave routine from the net rather than trying to remember the maths, and write directly into zbuf from the soundopenal.c al_givebuffer routine I get clean output. Example code:

        phase = 0;
                w = 100 * 2.0 * M_PI / (BUFLEN >> 1);
                for (c = 0; c < (BUFLEN >> 1); c++)
        {
            v = 32767.0 * sin(phase);
            zbuf[c*2] = v;
                        zbuf[c*2+1] = v;
                        phase += w;
                }

in the code above c is indexing stereo pairs.

SteveFosdick commented 7 years ago

So I wondered if this is any easier to read than the original for the buffer copy:

// Copy the mono input buffer to an interleaved stereo
// buffer to queue with OpenAL.

for (stereo_c = mono_c = 0; mono_c < BUFLEN; mono_c++)
{
    zbuf[stereo_c++] = buf[mono_c];
    zbuf[stereo_c++] = buf[mono_c];
}

Using that version with the SOUND command results in cleaner sound to my ear.

SteveFosdick commented 7 years ago

Ok, listening to some tunes on the emulated Music 5000 it certainly sounds familiar yet strange at the same time. Some notes seem noticeably out of tune and yes, there is some odd noise in the output.

An idea that just occurred to me - now you have opened a separate OpenAL stream at a higher sampling rate is sound_poll being called often enough from the 6502 to supply data at that sampling rate?

hoglet67 commented 7 years ago

@SteveFosdick - I think the original code is right, if rather unreadable:

for (c = 0; c < (BUFLEN >> 1); c++) zbuf[c] = buf[c >> 1];

It's right because in this file BUFLEN is defined in bytes:

#define BUFLEN (2000<<2)

I think it's done this way because that's what alBufferData() takes the length in bytes:

image

Your point about the rate of calling sound_poll is a good one. If the Music 5000 is uses any form of software modulation, then it's possible calling every 64us is a bit slow. This is easy to experiment with.

What I will try this morning is to replicate your sin wave experiment, and maybe look at the output on a scope.

hoglet67 commented 7 years ago

I've just tested the following:

void sound_poll()
{
  int i;
  for (i = 0; i < 2; i++) {
    double x = sin((2*M_PI)*cycle_pos*ram[0x2fff]/BUFLEN) * 10000;
    sound_buffer[sound_pos] = (int)x;
    cycle_pos++;
    sound_pos++;
  }
  if (sound_pos >= BUFLEN) {
        al_givebuffer(sound_buffer);
    sound_pos = 0;
  }
}

Which lets you set the frequency with ?&2FFF

The lower frequencies sound very good, and the waveform looks quite sinusoidal: img_0843

But these is possibly something wrong with the handling of high frequencies.

At 6.25KHz there is some noticeable amplitude modulation of the waveform. This looks to be about 350Hz, and the pk-pk amplitude is varying by about 300ms. img_0844

At 12.5KHz there is some evidence of resampling artifacts I think: img_0845 A sample is 32us, which is about 2/3 of a division. Imagine a blob at the crest/trough of each wave, and these are the sample points.

Here's the ^G beep: img_0846 It's much lower in level, only about 100mV RMS.

There is some evidence the the period is alternating between two different frequencies: img_0848 If you measure the variation, it's exactly 1/31.25KHz, so maybe this is to be expected.

Not sure what to make of this, or even if there is an issue here.

Dave

hoglet67 commented 7 years ago

I've managed to get Music 5000 running in the original Beech emulator, and compared that with my FPGA. Again, there is a massive difference with the "ppach" test track, with Beech sounding quite poor. @SteveFosdick let me know if you want to try this as well, and I'll upload the disks.

Beech seems to use a much higher quality audio pipeline, yet still sounds awful.

This post by Darren is interesting: http://www.stardot.org.uk/forums/viewtopic.php?p=120212#p120212

One major source of buzziness and distortion will be that I haven't implemented the low-pass filter stage which follows the DAC in the real device. The main reason for this is I just haven't had time. The other reason is I'm not sure of its characteristics!

However, for what it's worth, AudioServices::RunTick() handles downsampling. It actually oversamples all generated sound from the BBC (the 76489 and tape, if enabled, as well as the M5000) at 2MHz and adds that to an accumulator. Then, 44100 times a second (these times are precomputed and stored in graboffsets[]), the accumulator is divided down, placed in the output buffer and reset.

There is a huge difference between the software emulation and the FPGA. One (or both!) is probably incorrect.

The lack of low pass filter might be a red herring.

According to http://www.colinfraser.com/m5000/m5-eti.htm:

The left and right outputs each have a four-pole low-pass filter which reconstruct the Signals from their sampled and chopped form.

What I'm doing in the FPGA is summing the different channels, resulting in a new sample every 6MHz/128 = 46.875MHz.

I'm doing something similar in the B-Em's wrapper for Music 5000: https://github.com/hoglet67/b-em/blob/e1a2c30a2ea38c05b85d3bfe49d809c47be6091f/src/music5000.c#L174

At this point I'm feeling something is badly wrong in the Music5000 emulation, and that's the main source of noise.

What I think I'll try next is to write a Music 5000 test program that outputs a tone from a single channel, and compare with the Waveforms here: https://github.com/hoglet67/Music5000/wiki#standard-wave-definitions

hoglet67 commented 7 years ago

I think I've found evidence of a bug in the Music 5000 software emulation.

I'm using the test program here: https://github.com/hoglet67/Music5000/wiki#example-of-using-frequency-modulation

This generates a two-tone siren sound to test frequency modulation. On B-Em there is a nasty click when ever the tone changes frequency. This isn't present in the FPGA.

Here are some scope pictures:

FPGA: img_0851

B-Em: img_0852

So it looks like in the hardware implementation something is ensuring that channel switching happens at a defined point, and this is missing the the software emulation.

ThomasAdam commented 7 years ago

@hoglet67 -- this is a really interesting analysis to read. While I can't help at all with it, I'm learning a lot of things about oscilloscopes! Which one are you using? They seem rather expensive pieces of equipment.

SteveFosdick commented 7 years ago

Dave,

I think it's fair to say failing to implement a low pass filter after a DAC will result in poor sound, worse at higher frequencies than lower ones. I think Darren's argument is that by resampling to the sample rate the sound card is running at he's shifted this job onto the sound card. On the other hand his algorithm looks too simple to be a good one. I know that doesn't sound very scientific but I wonder if it is just one way to implement linear interpolation which is known to give poor results. So I am not sure we should dismiss the filter as irrelevant but maybe the failure to switch frequency at the end of a complete cycle of the current frequency is also pretty significant.

hoglet67 commented 7 years ago

@ThomasAdam -- glad your are enjoying it!

The scope is a circa 2000 HP 54622D. It's a mixed signal scope, so it has 2 analog channels plus 16 digital channels, and quite deep memory (2M samples).

Dave Jones did a nice EEVBlog tear-down: http://www.eevblog.com/forum/blog/eevblog-591-agilent-54622d-retro-mixed-signal-osciloscope-review-teardown/

It wasn't cheap (even second hand), but it's by far the most useful item of test equipment I have. I used to work for HP, so I have a soft spot for their test kit.

SteveFosdick commented 7 years ago

I also know you questioned the quality of the sound hardware on a PC so here are some samples captured in software by taking a copy of what is being output by the BBC in software. This is using pulseaudio which gives the feature to record what is being sent to the sound card via a standard program, e.g. audacity.

So, 62.5Hz zoomed out:

62-5-out

62.5Hz zoomed in:

62-5-in

1.2Khz zoomed out:

1-2k-out

1.2Khz zoomed in:

1-2k-in

6.25Khz zoomed out:

6-25k-out

6.25Khz zoomed in:

6-25k-in

9.4Khz zoomed out:

9-4k-out

9.4Khz zoomed in:

9-4k-in

12.5Khz zoomed out

12-5k-out

12.5Khz zoomed in:

12-5k-in

and finally a very different waveform for the ^G beep:

beep2

I think you can still see a lower frequency ripple imposed on those higher frequencies.

SteveFosdick commented 7 years ago

@hoglet67 We used to use some HP test kit back when we actually did any electronics. I remember we had one of the fancy scopes that had a colour screen and a blue "Auto" button that you could just press and it would find your waveform for you. We also had some HP computers driving automated test and measurement setups over HPIB.

hoglet67 commented 7 years ago

@SteveFosdick -- Do you have the "Internal Sound Filter" enabled? I'm wondering if that is affecting the expected square wave from ^G?

SteveFosdick commented 7 years ago

@hoglet67 Dave, yes I did. Without it I get a fairly clean square wave with none of the noise in the horizontal lines:

beep3

hoglet67 commented 7 years ago

Well, after some effort and with the assistance of BigEd, we found the issue that was causing the modulation discontinuity. The software emulation wrongly had two phase accumulators per channel pair that ran independently. The real hardware has just one that's shared between a channel and it's alternate. After fixing this, the sound is really good, and is very very close to my FPGA.

There is one other area of the emulation I'm suspicious about, and that's where the channel amplitude is applied. The emulation uses a multiply, where as the real hardware uses an add (as it's working in the log domain). I'll try changing this later.

hoglet67 commented 7 years ago

A couple more difference between the emulation and the real hardware sorted. This now sounds very close indeed. There's some code tidy up I want to do tomorrow, then I'll create a pull request for this branch.

BTW, I still think there is an issue with the sn76489 emulation causing a slightly "grainy" ^G but that's a separate issue.

I also still need to test the windows build.

SteveFosdick commented 7 years ago

@hoglet67 Out of interest do you know how close the FPGA sounds to the original hardware. I do have a Music 500 though I haven't yet dared to plug it in as I don't know if it would be subject to failing PSU capacitors in the same way as the BBC itself. It would also be a bit of work to try to get floppies with the right software on as I don't have an MMC at the moment.

Well done for solving the issue.

hoglet67 commented 7 years ago

@SteveFosdick -- It would be great to do a comparison with the real hardware. I think it should be quite close, but who knows!

We compared this video on YouTube: https://www.youtube.com/watch?v=3S54O2ecRAY with the same track on the FPGA and tonally it sounds very similar, but the FPGA was a bit less noisy, and less bandwidth limited. Not sure if that's just the recording that was lacking.

Looking at the pictures here: image http://chrisacorns.computinghistory.org.uk/8bit_Upgrades/Hybrid_Music5000.html there are no X2 capacitor to go bang!

I can point you to the right disc image.

Do you have Master or a Model B?

SteveFosdick commented 7 years ago

Model B.

ThomasAdam commented 7 years ago

This should be fixed...

hoglet67 commented 7 years ago

Actually, I think there's still an issue with the sn76489 emulation, but haven't had time to investigate.

I'll rename this issue to be specific to Music 5000, and open a new one.

ThomasAdam commented 7 years ago

Thanks.

hoglet67 commented 7 years ago

16