Blargg's blip_buf.c does anyone use it?

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Blargg's blip_buf.c does anyone use it?
by on (#155027)
Well now that i have grasped some DSP concepts i understand how Blargg's blip_buf.c SHOULD work, but i write "SHOULD" becouse i cannot get it working.

I want to know if someone uses it and maybe can help me.

Blargg's states in his .txt that:

Code:
This library resamples audio waveforms from input clock rate to output
sample rate. Usage follows this general pattern:

* Create buffer with blip_new().
* Set clock rate and sample rate with blip_set_rates().
* Waveform generation loop:
   - Generate several clocks of waveform with blip_add_delta().
   - End time frame with blip_end_frame().
   - Read samples from buffer with blip_read_samples().
* Free buffer with blip_delete().


I'm trying to make it to work in conjuntion with directsound and all i get is noisy distorded sound. I know that my DirectSound class is working well.

What i do is similar to following:

Code:
static blip_buffer_t* blip;

void init_blip()
{
blip = blip_new( 48000/10);                     //Blargg's states that 1/10 it's enough for the allocated buffer
blip_set_rates( blip, 1789773.0, 48000);     // NES NTSC clk and sample rate
}

void add_deltas(int cpu_cc)
{
    /* Well, this is what Blargg's states: it should run on a loop and add the "deltas" */
    for (int i = 0; i < cpu_cc; i++)
    {
        float sample = GetApuOutput();      //in float values
        sample *= 0x07FFF;                     //i don't know this is right
        blip_add_delta(blip, Sound.cpu_cc, (int)sample);
        Sound.cpu_cc++;
    }
}

/* He also states that at the end of a frame you should do something like this -> */
void write_buffer()
{
   if (!SoundOut.IsPlaying())       // this is DSound things
      SoundStartFirstTime();

   blip_end_frame(blip, Sound.cpu_cc); // here i have a doubt: the second parameter, are the cc elapsed. It's a time var, i don't know
   int avail = blip_samples_avail(blip); // Get the samples available
   
   blip_read_samples(blip, Sound.buffer, avail, 0);  //Copy the blarggs buffer to my DSound buffer in mono
   SoundOut.WriteBuffer((char *)Sound.buffer, avail * 2); //Write to my DSound buffer
   Sound.cpu_cc = 0;                                            //reset cpu_cc cycles counter

}

void close()
{
    blip_delete(blip);
}



add_deltas(cpu_cc) is called after i emulate one CPU instruction and pass it to this function.

From the point of view of theory of what blip_buf.c does is great, but i cannot make it work.

I hope anyone has used this blip_buf.c and can help me a little.
Re: Blargg's blip_buf.c does anyone use it?
by on (#155049)
Making some testing with blip_buf i discovered that to get sound i have to use the standard way of taking the sample every 37.5~ so cpu clocks for 48000.
I only tested with pulse1.
Having 2 counters, one for total cpu cc for the frame and another one for counting cpu cc to get the sample data every 37.5~ i got that making this way:

Code:
    if (Sound.cc_counter >= samples_div)         //samples_div = 37.5~
   {
   int sample = GetPulse1Out() ;
   sample *=8;
   blip_add_delta(blip, Sound.cpu_cc++, sample);
   Sound.cc_counter -= samples_div;
   }
   else
   blip_add_delta(blip, Sound.cpu_cc++, 0);

   Sound.cc_counter++;


It puts a delta of "sample" every 37.5~ cpu clocks and "0" for the reminder ones.
This way i can hear pulse1 and im facing 3 problems: volume, mixing and sound sync.

As you can see sample *=8 is tolerable, but for *=16 for example, don't. I don't know why. Ttaking into account that if the value of the int value got by GetPulse1Out() is between 0-15, 15*16 = 240 and a valid positive value for a 16 bit sample.
I don't have idea why this. It's a pitty that Blargg that used to write in this forum has dissapeared (maybe he is too busy with work).

I neither don't know how to mix the channels. Any help with this? this is not a blip_buf thing, instead a sound thing I have searched google for help, but no luck.

The sync problem is another topic that i will leave for another post.
Re: Blargg's blip_buf.c does anyone use it?
by on (#155076)
The big thing about blip buffer is that it's pretty hard to figure out what it does or how to use it.
So then you try to write a similar piece of code that does something like what it does, then gradually, your code ends up becoming blip buffer, then you understand how to use it.
Re: Blargg's blip_buf.c does anyone use it?
by on (#155114)
Anes wrote:
I neither don't know how to mix the channels. Any help with this? this is not a blip_buf thing, instead a sound thing I have searched google for help, but no luck.


Try a search for PCM mixing, or PCM audio mixing, those should return useful results.

Basically, it involves adding the channels together, and preventing the result from exceeding the maximum value (clipping).
Re: Blargg's blip_buf.c does anyone use it?
by on (#155121)
Dwedit wrote:
The big thing about blip buffer is that it's pretty hard to figure out what it does or how to use it.
So then you try to write a similar piece of code that does something like what it does, then gradually, your code ends up becoming blip buffer, then you understand how to use it.


I have make some advances over it. Also i have discovered (as the blip_buf.txt says) that i don't need to add a Delta every cpu clock. So i add it every 37.5~ clock. This way is simplier the cpu_cc counter adds cpu_cc+= 37.5~ and call blip_add_delta() with that clock.

Quote:
Try a search for PCM mixing, or PCM audio mixing, those should return useful results.

Basically, it involves adding the channels together, and preventing the result from exceeding the maximum value (clipping).


I worked around this, and i pre-mix the APU channels before sending to blip_buf. So i don't need by the moment to mix DSP audio.

But, my big question now is this:

Code:
    float sample = GetApuOutput();                    //Get float mixed channels APU data
    sample *= 10.0;                                        //I do this to have an integer type, since the value returned before is > 0
    blip_add_delta(blip, Sound.cpu_cc, sample);   //Add the sample
    Sound.cpu_cc+=samples_div;                      //The cpu cc counter i mentioned before (usually set to 37.2~)


I don't know why i cannot work with multipliers greater than sample*= 10.0. It seems blip_buf.c is doing something complex internal that if i increse to for example 100.0 the sound is cliped and distorded.

Any idea??

Edit: I get extremely low sound volumes!!
Re: Blargg's blip_buf.c does anyone use it?
by on (#155124)
In your original code most of the time you'll be getting zero samples available. Does your buffering code work properly if you call SoundOut.WriteBuffer() with zero samples?

You don't seem to be adding deltas, rather the absolute sample values. You must keep track of the current amplitude and add the difference between that and the new amplitude. This would account for awful sounds you'd get, though not explain why waiting every N clocks would make it work (maybe zero samples was why).
Re: Blargg's blip_buf.c does anyone use it?
by on (#155126)
Thanks for answering Blargg.

blargg wrote:
In your original code most of the time you'll be getting zero samples available. Does your buffering code work properly if you call SoundOut.WriteBuffer() with zero samples?


Yes, you were right, i have to call blip_add_delta every frame cpu cc to get sound. I.e: putting "0" in not significant cc and "sample" in significant ones (every 32.7~ cc). That way i get 798 samples count free when i write to the buffer when the frame ends (nerly the 800 samples needed for 48khz).
SoundOut.WriteBuffer() works well, it was written by Disch and do a decent job.

blargg wrote:
You don't seem to be adding deltas, rather the absolute sample values. You must keep track of the current amplitude and add the difference between that and the new amplitude. This would account for awful sounds you'd get, though not explain why waiting every N clocks would make it work (maybe zero samples was why).


I didn't know that!! Im going to see if i can implement it.

Thanks.
Re: Blargg's blip_buf.c does anyone use it?
by on (#155131)
It works like a charm!!! Thanks Blargg
Re: Blargg's blip_buf.c does anyone use it?
by on (#155136)
Well now i have problems with sound sync. Blip_buf is returning 798 and 799 oscillating free samples count.
I did a hack inside the c code that sets m->avail to 800 and it seems sound doesn't desync, but i would like not to have this hack.

In the blip_buf.c code there are a lot a variables that this "avail" depends on and it's complex code which i don't understand.
How to get the 800 avail right?
I have set cpu time to 1789773 and Sample Rate to 48000.

I got 29780-29781-29782, etc oscillating clocks for the frame. I have tried putting a this fixed value in blip_end_frame() and it still returs 798-799. Putting the cc_counber (the oscillating mentioned above) for the frame results in the same thing.

Anyway if i have to live with the hack, i do have.
Re: Blargg's blip_buf.c does anyone use it?
by on (#155138)
You shouldn't get exactly 800 samples every frame since NES frames are not exactly 1/60 second long. Can your sound code handle just getting 798/799 samples per frame rather than needing exactly 800? This probably touches on the issue of audio and video running at the same rate.

If you want to get a fixed number of samples per frame, use blip_clocks_needed to find out how many clocks to end the frame with to get an exact number of samples total. This should work, though I haven't tested nor used blip_buf in a while:

Code:
int total_samples = 800;
int samples_needed = total_samples - blip_samples_avail( blip );
int clocks_needed = blip_clocks_needed( blip, samples_needed );
blip_end_frame( blip, clocks_needed );


This will of course introduce very small extra audio delays at the end of each frame between the last CPU cycle and the first of the next frame.

You also don't need to end frames every CPU instruction. You could end blip's frame every PPU frame and then reset Sound.cpu_cc to 0 for the next frame.
Re: Blargg's blip_buf.c does anyone use it?
by on (#155139)
Also consider that the NES's actual frame rate is 1789773/29780.5 = 60.0988 Hz, and 60÷60.0988×800 = 798.685. This may be causing the alternation between 798 and 799. If you're synchronizing the emulated system to a 60 Hz time base, you'll want to underclock the emulated CPU by about 0.16%: 29780.5 * 60 = 1786830 Hz.
Re: Blargg's blip_buf.c does anyone use it?
by on (#155141)
Thanks to both.
I underclocked the CPU at 1786830 hz in blip_buf and also adjusted other variables that depended of real NES clocks.

Problem solved :mrgreen:

Blip_buf rocks!!