By just sending the value written to the dac (volume), it plays fine, except for an high frequency sound on background. I suspect it's because of +0x40 in every sample. Any ideas of fixing it?
I'm confused by the qualifier "unsigned", because the device is fundamentally an unsigned one. What is this an alternative to?
A fixed bias should be silent, unless you're doing something very strange to your sound; maybe a broken highpass filter of some sort?
Is the high frequency sound a specific frequency? (What frequency?)
You must listen yourself. Here's my wave file.
First part is unsigned; later, signed samples.
On the NES, DPCM encoded samples intrinsically always encode an audible error signal.
For a naïve encoder that doesn't try to do any noise shaping (which is to say almost all of them), this error signal is a whine at half the sampling rate.
Is possible to remove it & keep the playback as unsigned samples?
Why did you use .rar instead of .zip or .7z?
If you're referring to hiss in the drums of the world 2 overworld, that hiss is encoded in the sample itself. I can hear it clearly on my NES through A/V out. I can't hear it so well in RF out because high frequencies are EQ'd so far down. To remove it during playback, you can apply a low-pass filter to the whole mix, as the RF output does.
Yes, you could add an extra fictitious band-stop filter at the Nyquist rate. A two-sample moving average should be adequate (after the delta encoding→linear count conversion, but before resampling to the sound card's native rate). I don't know how audible this lowpass filter would be beyond that: delta encoding is already really bass-heavy.
tepples wrote:
Why did you use .rar instead of .zip or .7z?
Win
RAR tepples wrote:
If you're referring to hiss in the drums of the world 2 overworld, that hiss is encoded in the sample itself. I can hear it clearly on my NES through A/V out. I can't hear it so well in RF out because high frequencies are EQ'd so far down.
Okay.
Zepper wrote:
First part is unsigned; later, signed samples.
Why is "unsigned" so much quieter than "signed"?
It depends on whether "unsigned" means from the NES point of view or from the PC point of view. Not knowing which, I shall answer both.
If from the NES point of view: All five 2A03 channels are unsigned, outputting only positive amplitudes. But doing so creates a DC bias in the output, and a high-pass filter is needed to remove this. The audio path in the NES Control Deck includes such a filter.
If from the PC point of view: There are two conventions for representing PCM audio. "Unsigned" uses all positive integers within a built-in bias of half of the range (0 to 255 or 0 to 65535). "Signed" uses signed integers (-128 to 127 or -32768 to 32767). The Allegro 4 library (used for audio output, among other things, in Zepper's RockNES emulator) uses unsigned samples, as does port $4011 on the NES. The Game Boy Advance audio hardware uses signed 8-bit samples. RIFF WAVE files (.wav) traditionally use unsigned 8-bit samples or signed 16-bit samples. To convert from one convention to the other, flip the most significant bit.
1. Let's take the NES APU sample generation - unsigned samples. The DMC output has an audible hiss caused by a +0x40 factor. Back in old times, the DMC output was
value-0x40 to center the wave (signed samples).
2. For PC sample generation, well... Allegro uses unsigned samples (16 bits = 2 bytes per sample), and the emulator generates signed samples. Example: the square wave duty cycle has a volume level (
00-0F); for signed samples, the zero part becomes a negative sample. So, a duty cycle
0 1 1 1 1 1 1 0, or in volume levels
00 0f 0f 0f 0f 0f 0f 00 should output as
-0f +0f +0f +0f +0f +0f 0f -0f. Argh, in other words:
Code:
output = 0 != duty ? volume: -volume;
3. At anyway, the final PC wave is built by
value ^ 0x8000 for the Allegro sound wave. I'm not going into this part.
Zepper wrote:
1. Let's take the NES APU sample generation - unsigned samples. The DMC output has an audible hiss caused by a +0x40 factor. Back in old times, the DMC output was value-0x40 to center the wave (signed samples).
A constant bias is
silent. This does
not create "an audible hiss". There's no "-0x40" or "+0x40" on the output, centering is accomplished with a
highpass filter.
Either the audible hiss is caused by
something else, or something is very broken in your audio output if you think adding or subtracting 64 to the output affects it.
Yeah, you need to add in the "world's simplest highpass filter". This post may contain mistakes or errors, and is psuedocode done from memory...
Stage 1: Instead of using actual volume samples, deal in differences between current sample and last sample.
For example, some square wave might be like 0,0,0,0,32,32,32,32,0,0,0,0,32,32,32,32...
Instead express it as 0,0,0,0,32,0,0,0,-32,0,0,0,32,0,0,0...
Then when you want to output actual samples, you would do something like this:
int wave_value
wave_value += wave_change[i] << 24
output_wave[x] = wave_value >> 16
bonus: you can prevent overflows on wave_value easily this way.
Stage 2: The actual highpass filter itself
you can do wave_value = wave_value - (wave_value >> 24) after every sample to make it slowly decay to zero, this will eliminate DC biases. You still add the wave changes to wave_value as normal.
Note: not sure on the actual numbers for the filter. Someone else please fix my mistakes...
File 001.wav - first half plays signed samples (filtered); next half plays unsigned samples.
File 007.wav plays 4011_value-0x40 (center-line).
Between the samples played in this SMB3 recording should be silence where they stop. When a sample is finished playing, the DMC will be outputting a flat signal, which is not what you have, here. In your 001.wav there is a constant whine at ~13700 hz, and it never stops.
I don't think this whine is related to the intrinsic DPCM samplerate, otherwise it would change pitch between the two samples being played, because they use different DPCM frequencies.
I would guess that you have a problem with your oversampling/downsampling method (or some other post-processing you're doing), and somehow this problem manifests differently depending on this bias you've applied. I think this "-0x40" kludge you keep suggesting is probably just a bandaid on a problem that's not directly to do with the DPCM.
The amplitude of the whine seems to be directly related to the current magnitude of the signal. Your 007.wav still has the whine, it's just quieter because you moved the signal closer to 0. The problem is not that the signal is biased, you're just making the problem quieter-- what you need to do is eliminate the cause, and this "-0x40" idea just isn't going to do that. I think you need to look outside your DPCM implementation. This idea about "signed" or "unsigned" DPCM samples is a red herring. The bias shouldn't make an audible difference; there is something else wrong with your audio processing.
Don't worry about highpass filters yet. They're not going to fix the problem either, they're just how an NES would actually centre the waveform. The bias isn't the problem here, though, so implementing a highpass filter won't help. Similarly, implementing a lowpass filter to remove the whine is also incorrect (might hide the problem, but also kills other frequencies that aren't part of the problem). Fix the whine first, leave the filters out until you can get rid of it.
The noise can come from an incorrect window function selected for the interpolation. I don't recommend using any window function that both ends are not zero (e.g. Hamming), I had a problem similar to yours in a toy resampler I made, a DC bias made some noise, and the noise would get bigger if the bias was increased.
Found the problem. The output volume was coming directly from $4011 writes! Instead, it should come from channel update. In raw mode ($4011 samples only, no DPCM unit), the frequency reload should be 1 (cycle), instead of taking the first entry from the DMC frequencies table, or 0x1AC (NTSC).
It worked like a charm. Just listen the file.
Zepper wrote:
It worked like a charm. Just listen the file.
The high frequency whine is still there. Look at the silent section at the start of this recording; why isn't it actually silent?
Because the value written to $4011 is never zero. In unsigned samples mode, the value written to $4011 is the volume level output to dac. In short words, there isn't any kind of sample filtering. If you don't believe me, just log the values written to $4011.
I'm not asking why it's not 0, I'm asking why it's not flat (flat = silent). There is a high frequency whine there instead of silence, which you asked about in the very first post in this thread.
This doesn't have anything to do with $4011, it's a problem with your sound generation in general. Maybe with downsampling, as has been suggested, or maybe something else.
Battletoads uses $4011 for raw dmc, so how's unrelated?
On channel update, I do (value&$7F) << 8. The value is never zero.
Look at your file. Look at the very beginning, before DMC is playing.
Attachment:
Battletoads (U) 000.png [ 3.75 KiB | Viewed 2407 times ]
Why is this here?
It is not DMC. It is a bug somewhere in your emulator.
I use GoldWave to view the waveform... and yeah, I know of this problem. But I have no clue what's up.
As I showed up... it's the $4011 value & $7F << 8 (16-bit sample). The value written is never zero.
I have two methods of resampling, but both sum (+) the current sample to an adder. One counts the N number of added samples and divide the adder by N; another do the same, but instead of using a division by N, I shift right the adder. Better results are with adder >> 5.
This is my resample value based on the NES master clock:
Code:
* The exact sample rate of the NES audio DAC itself is 39375000/22 Hz = 1.7898 MHz.
resample:
(39375000 / 22) / 48000Hz (PC rate) =
= 39375000 / 48000*22 (both x22) =
= 393750 / 480*22 (both DIV 100) =
= 393750 / 10560 =
= 39375 / 1056 (both DIV 10) = ~37 samples per sync.
both DIV 3 => **13125 / 352**
In each apu update:
Code:
decay_master = 13125;
decay_value = 352;
decay_master -= decay_value;
if(decay_master <= 0)
//calculate the sample and send it to the wavebuffer.
APU source code may be avaliable if you're willing for it.
Given prior guesses: try bypassing your resampler? (Just dump the 1.8MHz audio file to disk)
I wouldn't be surprised if this turned out to be "some weird Allegro thing", somehow. I believe the version of Allegro being used here is 4.4, while 5.2 is the latest (edit: 5.2 appears to be latest, but I did find some prebuilt binaries that are labelled 5.3.0...). Not sure what type of audio layer is being used (DirectX/DirectSound I'd guess). The changelog for 5.0.0 to 5.0.1 has an entry that says "Play silence where needed in DirectSound driver", which made me chuckle.
lidnariq wrote:
Given prior guesses: try bypassing your resampler? (Just dump the 1.8MHz audio file to disk)
Fine.
Here's the raw dump, 16-bit sample, low byte first.
Yup, it's your resampler.
The raw 1.8MHz sample rate output here is perfectly clean.
lidnariq wrote:
Yup, it's your resampler.
The raw 1.8MHz sample rate output here is perfectly clean.
I didn't get it.
How so? What's exactly the difference between the waves?
Care to take screenshots of the waveforms for comparison, plz?
Top: 1.8MHz, perfectly clean.
Bottom: 48kHz, bonus noise.
Hmm... I see. And I have to apologize for the language barrier.
Yes. I though we were talking about the DMC waveform not being centered and without silence when it should be.
Like...
Code:
|-------| |-------|
| | | |
| |____| |____ (silence,
| but not zero)
|
| attack
_______
begin
(silence = 0)
There's a noise in the waveform, yeah, I had seen it.
Let me start with square waves. This is the best music to test so far.
Tell me if something's obviously wrong, please.
Nothing's obviously wrong. No additional noise content is present, transitions look roughly consistent with a slightly overdamped interpolator.
OK, I'm adding an highpass filtering, but one thing is bugging me. With code seems better to explain...
Code:
int output_L; //L = left channel, or mono.
int previous_sample;
//at every APU clock
int value = pulse_1_volume + pulse_2_volume + ...
output_L += (value - previous_sample) << 8;
previous_sample = value;
num_of_updates++;
//on resample (PC sample generation, after appox. 37 APU updates)
//decay
output_L -= output_L >> 7;
unsigned short *wavebuffer = (output_L / num_of_updates) ^ 0x8000; //here's my question!
The sound output isn't fine - it's not "crystal and clear", but... a bit noisy.
I had perfected the unsigned samples - no errors (listen the previous file), but on highpass, it's not fine.
What I'm doing wrong after all...!?
There's so many different little things...
Quote:
output_L += (value - previous_sample) << 8;
previous_sample = value;
There's no need to multiply by 256 yet, you may as well skip that for now.
This is an extremely high frequency FIR high-pass filter, with a corner frequency at one sixth of the sample rate. Since the sample rate here is 1.8MHz, any audio that comes out is going to be awfully quiet... probably why you added the <<8.
Quote:
unsigned short *wavebuffer = (output_L / num_of_updates) ^ 0x8000;
I think it's very likely that this varying num_of_updates is the source of the noise you had with the original resampler.
Someone (ages ago) told me to do that for DMC/RAW PCM samples. So, I'm using that method for the APU output.
lidnariq wrote:
There's so many different little things...
Quote:
output_L += (value - previous_sample) << 8;
previous_sample = value;
There's no need to multiply by 256 yet, you may as well skip that for now.
This is an extremely high frequency FIR high-pass filter, with a corner frequency at one sixth of the sample rate. Since the sample rate here is 1.8MHz, any audio that comes out is going to be awfully quiet... probably why you added the <<8.
If I take out the << 8, how's made the decay >> 7 ???
Well, any suggestion? I don't know how to resample & apply the highpass filtering. That's the problem.
Zepper wrote:
output_L -= output_L >> 7;
That line is equivalent to
output_L = outputL*127/128; It's not actually related, unless you see signs of clipping or are losing audio quality by accidentally discarding bits off the little end.
Quote:
Well, any suggestion?
Ok, so you have a no-obvious noise resampled version without the highpass. What changed between that version and the one you just posted? Just the
(value-previous_sample) ?
I'm confused. What "versions" do you mean? The resample method is just summing all the generated samples and divide by the number of samples.
Plus, the "<< 8" is for 16-bit samples. I can't simply take it out.
What is the difference in the source code that you made between the one that made the audio in
this postand the code fragment that you posted?
I recorded 3 wav files.
One using RockNES 5.24 signed samples.
The other two uses 5.25 - unsigned samples (perfect) and with the highpass filter (noisy).
For unsigned samples? No filtering, just output_L += pulse_1 + pulse_2 +..., then *wavebuffer = output_L / num_of_samples.
Just added
high pass from wikipedia... but without
your drop of magic, it wouldn't be working.
y(i) := a * (y(i-1) + x(i) - x(i-1))y(i) is the new sample
y(i-1) is the previous sample
x(i) is the input sample
x(i-1) is the previous input sample
a is your magic, or sample*127/128; otherwise,
for some reason, I was only getting unsigned samples. Care to explain?
Just compare the files.
You elided enough of the code that it was hard to tell exactly what was going on.
It looked like the output -= output>>7 was happening on isolated samples after the sample rate change; in that case it's just a simple volume adjustment.
If that volume adjustment happens BEFORE the filtering is applied, then it will change the exact frequencies that are filtered.
If you used a simple first-order output-delay like this:
y[n] = a·(y[n-1] + x[n] - x[n-1]) →algebra→
y[n] = a·y[n-1] + k·(x[n]-x[n-1]) →Z transform→
Y = a·Yz¯¹ + a(X-Xz¯¹) →algebra→
Y(1-az¯¹) = a(1-z¯¹)X →algebra→
"transfer function" = Y/X = a(1-z¯¹)/(1-az¯¹)
fraction on the right is 0 when z is 1.
fraction on the right is infinite when z is 1/k.
If a is 127/128, this corresponds to a corner frequency of .000724597 times the sample rate, or 1300Hz at 1.8MHz, and 35Hz at 48kHz.
One last thing. Anything about sample*k-1/k, with k a number power of 2? There's no audible differences as far as I can tell you - the waveform slightly changes.
- like 127/128 or 63/64 or 31/32 -
The corner frequency of this highpass is very approximately samplerate÷k÷10, so you're not going to get a very noticeable difference in the range of factors you've listed here.
Fine. Since I wasn't lucky with the low pass option (couldn't get signed samples), I'll keep this high pass filtering. Amazing and much more cleaner (and crystal) sound output.