It is currently Mon May 20, 2019 8:46 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 13 posts ] 
Author Message
 Post subject: Cracking Sound for APU
PostPosted: Sat Feb 16, 2019 12:39 am 
Offline

Joined: Tue Apr 17, 2018 3:40 am
Posts: 20
I've mostly implemented the APU, but I'm unsure of how to implement the timing of actually outputting crisp sound.

First, I tried to calculate the number of APU cycles per sample (NTSC_FREQ / sampleRate), and that worked pretty well, but there was minor crackling when VSync was on. When there was no frame limiting, it sounded perfect, but it was much slower than the video. I've attached the audio in cycle.mp3
Code:
this->sampleSum += this->generateSample();
if(this->cycleCount >= this->cyclesPerSample) {
   Sint16 sample = static_cast<Sint16>(globalVolumeFactor * this->sampleSum / this->cycleCount);
   SDL_QueueAudio(this->audio.device, &sample, sizeof(sample));
   this->cycleCount = 0;
   this->sampleSum = 0;
} else this->cycleCount++;


Then, I tried to calculate the time between each cycle (1 / sampleRate), but in that case, there was significantly more crackling, to the point where it was practically unrecognizable. I've attached the audio in time.mp3
Code:
this->sampleSum += this->generateSample();
double curTime = glfwGetTime();
if((curTime - this->prevTime) * Audio::sampleRate >= 1) {
   this->prevTime = curTime;
   Sint16 sample = static_cast<Sint16>(globalVolumeFactor * this->sampleSum / this->cycleCount);
   SDL_QueueAudio(this->audio.device, &sample, sizeof(sample));
   this->cycleCount = 0;
   this->sampleSum = 0;
} else this->cycleCount++;


By looking at Audacity, I've realized that the crackling is caused by the period of silence after one segment of audio has finished playing but before the next one is queued. How would I get crisp sound without any crackling while still having the audio speeding up if the framerate increases?


Attachments:
File comment: Time
time.mp3 [175.41 KiB]
Downloaded 85 times
File comment: Cycle
cycle.mp3 [151.8 KiB]
Downloaded 80 times
Top
 Profile  
 
PostPosted: Sat Feb 16, 2019 12:56 am 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 8360
Location: Seattle
Sadly, you've discovered that you're torn between two masters. (Different clock domains). You will have to pick one that will lose.

One recent emulator - I think nintaco? nemulator - dynamically resamples audio by a couple permille to make the audio and video rates match.

Other emulators just accept tearing, stuttering, or dropped frames in video.

An emulator that is specifically targeting a device that emits both audio and video via HDMI has both video and audio in the same clock domain, so in this narrow case, you can carefully adjust the emulated clock rates to keep the two in sync.


Last edited by lidnariq on Sat Feb 16, 2019 8:37 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Sat Feb 16, 2019 1:20 am 
Offline

Joined: Tue Apr 17, 2018 3:40 am
Posts: 20
If I'm forced to choose one, I'd rather choose the one timed with a timer instead of counting the clock cycles. However, the audio is practically unrecognizable if I do it that way because there is almost as much crackling as there is useful sound. Am I using the wrong function or algorithm to time it or is it just unfeasible to do it that way?

You seemed to imply that it can be done even with a timer with only slight mistakes, so I'm assuming that something in my code is causing the extremely excessive crackling.


Top
 Profile  
 
PostPosted: Sat Feb 16, 2019 2:25 am 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 8360
Location: Seattle
So, the problem with "time.mp3" isn't the dropouts. (You're dropping waaaay too many samples for that to be what's wrong)... you've got to be doing the math wrong instead.

How do you determine cycleCount? I'll point out that 1789773Hz ÷ 48000Hz isn't particularly close to any integer.

(Nor, for that matter, is "slowed down NES CPU clock for exact 60Hz video" meaningfully closer - 60 × 262 × 341 × 4 ÷ 12 = 1786840 Hz)


Top
 Profile  
 
PostPosted: Sat Feb 16, 2019 5:27 am 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4071
Location: A world gone mad
I can't tell what the heck is going on with time.mp3. With cycle.mp3, this honestly sounds like an audio playback buffer overrun or underrun, i.e. you're either being too aggressive with your latency (I suspect it's this) or not aggressive enough; I can reproduce this when using something like FCEUX and setting the latency slider to something extremely low, then letting the emulator lose/regain focus. Other emulators for all sorts of consoles (NES, SNES, everything) have this exact problem too, while others do not. It's all about implementation, all the way down to what OS-level timer functionality you're using.

You probably want to read this thread and the references linked within it; James (author of Nemulator) goes over his model, which I believe is what lidnariq is describing: viewtopic.php?f=3&t=15405

From what I understand, the "most stable" way to keep A/V in sync is to actually "sync off the audio", not off the video. Video refresh rate is too susceptible to variance (see: monitor refresh rate, Vsync on/off, full-screen vs. windowed, GSYNC/FreeSync, etc.).

I also hope that Sour provides some details about how he did his A/V synchronisation as well as "overall timing sync" in Mesen, since quite honestly it's the one emulator I use that is perfect in this regard -- no tearing, stable framerate, no audio anomalies. Nestopia comes in at a close second (it worked better for me on XP, honestly), followed by FCEUX, followed by anything else I've tried (most of the latter have some degree of audio crackle/oddities).


Last edited by koitsu on Sat Feb 16, 2019 8:34 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Sat Feb 16, 2019 8:39 am 
Offline
NESICIDE developer
User avatar

Joined: Mon Oct 13, 2008 7:55 pm
Posts: 1089
Location: Minneapolis, MN
One technique I found helpful is outputting the sound to a WAV file. But not just the sound. With a WAV file you can create multiple channels of...anything. In one channel, for example, I output a ramp. Effectively a sawtooth that is just an integer I increment every time I create an audio sample. I also output the produce/consume pointers of my audio buffer so I can see how they move. Then you can use Audacity or just write yourself a quick script to parse out that channel data into some analysis tool. I used Excel to graph long times and look for anomalies.

Also, to take care of the "fractional" nonsense, I use a float to represent how many samples I need to generate at 1.789whateverMHz before I need to *consume* one for 44.1KHz. This works out to something like 40.58godawful. But then every time I produce a sample I add 1.0 to a float counter. Then every time that float goes above 40.58godawful, I *subtract* 40.58godawful from it. I see you are resetting your counters to 0. That might lead to misses in the fractional world. Because things don't line up perfectly, when I subtract from the counter my counter will go back to "nearly" zero. But not (usually ever) *exactly* zero. So that means that it'll take a varying amount of time to get back to the point where I need to generate another 44.1KHz audio sample.

My approach isn't perfect by any means...but I think it sounds pretty nice.


Top
 Profile  
 
PostPosted: Sat Feb 16, 2019 9:38 am 
Offline

Joined: Sun Feb 07, 2016 6:16 pm
Posts: 667
koitsu wrote:
I also hope that Sour provides some details about how he did his A/V synchronisation as well as "overall timing sync" in Mesen
It's fairly simple, all things considered. The FPS is regulated by a high precision timer, once a frame is done rendering and sent to the video card, the emulation thread sleeps and waits ~16.66ms minus the time it took to render the frame. If there's a slowdown for some reason, the core will compensate and run some frames faster than 60fps to catch up. The entire core runs in a separate thread from the UI, so UI actions have no impact on it (e.g moving the window, using menus, etc.), which makes this somewhat simpler to manage, too.

For audio, it monitors how far ahead of the playback the sound card is (by checking the current play position vs the last write we did) to calculate latency. If average over the last second deviates too much from the target latency (e.g by more than 3ms), it will dynamically adjust the sample rate by a very small amount (something like 0.025% for each millisecond of gap above 3ms, up to a max of 0.2% or so). It also adjusts the resampling rate a bit more aggressively the longer the average latency is outside the +/- 3ms target range).

That being said, the end result feels relatively simple, but I rewrote both the video and audio timing code something like 3 times each over the past 3 years, so...


Top
 Profile  
 
PostPosted: Sat Feb 16, 2019 9:47 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21388
Location: NE Indiana, USA (NTSC)
The clock ratio can be expressed without "godawful" floats if you think rationally.

NES CPU rate: 39375000/22 Hz
audio rate 44k: 44100 Hz
audio rate 48k: 48000 Hz

Factor each:
NES CPU: 2^2 * 3^2 * 5^7 * 7 / 11 Hz
audio rate 44k: 2^2 * 3^2 * 5^2 * 7^2 Hz
audio rate 48k: 2^7 * 3 * 5^3

NES CPU to audio rate 44k ratio: 5^5 / (7 * 11) = 3125/77
NES CPU to audio rate 48k ratio: 3 * 5^4 * 7 / (2^5 * 11) = 13125/352

So at 44k, you can add 77 to a counter every CPU cycle, wrap it at 3125, and leave the floating for Balloon Fight and Kirby's Adventure.

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Sat Feb 16, 2019 10:11 am 
Offline
NESICIDE developer
User avatar

Joined: Mon Oct 13, 2008 7:55 pm
Posts: 1089
Location: Minneapolis, MN
tepples wrote:
if you think rationally.

Yeah that's my problem. I tend to float through life. :)
Thanks for Doing The Math for me.


Top
 Profile  
 
PostPosted: Sat Feb 16, 2019 5:02 pm 
Offline
User avatar

Joined: Fri Nov 19, 2004 7:35 pm
Posts: 4172
There's even one more option: Mess with your monitor refresh rate to get a true 60.098Hz refresh rate. There is a tool called "Custom Resolution Utility" which will let you tweak your monitor timings.

_________________
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!


Top
 Profile  
 
PostPosted: Sat Feb 16, 2019 7:58 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 10:59 pm
Posts: 1464
koitsu wrote:
You probably want to read this thread and the references linked within it; James (author of Nintendulator) goes over his model, which I believe is what lidnariq is describing: http://forums.nesdev.com/viewtopic.php?f=3&t=15405

I assume you meant "Nemulator" there...

_________________
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.


Top
 Profile  
 
PostPosted: Sat Feb 16, 2019 8:34 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4071
Location: A world gone mad
Quietust wrote:
koitsu wrote:
You probably want to read this thread and the references linked within it; James (author of Nintendulator) goes over his model, which I believe is what lidnariq is describing: viewtopic.php?f=3&t=15405

I assume you meant "Nemulator" there...

Yup, sorry! I'll edit it. (Quietust here is the author of Nintendulator :-) )


Top
 Profile  
 
PostPosted: Sun Feb 17, 2019 5:18 pm 
Offline

Joined: Tue Apr 17, 2018 3:40 am
Posts: 20
I decided to just output an audio sample every number of cycles and increase the sample rate to 48000 from 44100, and it seemed to get rid of most of the crackling. To get rid of the last bit, I decided to add CPU timing to limit the framerate to 60.09, and that seems to get rid of the other crackling.

But the way I implemented the frame limiting isn't ideal. I tried using std::this_thread::sleep_until or sleep_for but it always overslept and resulted in around 55 fps. So I used a while loop to just check if enough time has passed, and it was extremely accurate, but I've read that it's cpu intensive and not recommended. Is a while loop the only way to do it since the time interval is so small or are there better ways to do it?


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 13 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: 133MHz, Bing [Bot], Google [Bot] and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group