yokemura / Magical8bitPlug2

GNU General Public License v3.0
301 stars 19 forks source link

[BUG] Aliasing noise #18

Open sdhizumi opened 3 years ago

sdhizumi commented 3 years ago

Describe the bug Magical8bitPlug2 has an aliasing noise, increase or decrease depending on the frequency rate of the host. I think this is not a correct sound of an old 8bit game console.

To Reproduce

  1. Open project (I checked 44.1kHz, 48kHz, and 192kHz project)
  2. Load M8P2
  3. Play something note

Expected behavior Played something note but it includes an aliasing noise. This noise affects the impression of the high frequencies, decreases with higher sampling rates.

I tried to compare it with the actual Gameboy's waveform. (Sorry I can't test with FC/NES quickly.)

Screenshots All images captured by Voxengo SPAN Plus with these settings: Window = HANN Overlap = 50.0 Avg Time = 500 Type = RT AVG Block Size = 2048

ANTI-ALIAS = OFF ALIGN 0 DB = OFF Offset = OFF Smooth = OFF

Freq Lo = 20.0 Freq Hi = 20.0k Range Lo = -96.0 Range Hi = 0.0 Slope = 4.50 (Probably it can be setting the same settings on SPAN, free edition.)

Played 440Hz 50% Pulse note - on 44.1kHz: M8P2_A4_PL50_44 1K_20-20k M8P2 DMG_A4_PL50_44 1K_20-20k Gameboy APU

on 48kHz: M8P2_A4_PL50_48K_20-20k M8P2 DMG_A4_PL50_48K_20-20k Gameboy APU

on 192kHz: M8P2_A4_PL50_192K_20-20k M8P2 DMG_A4_PL50_192K_20-20k Gameboy APU

NOTE - Gameboy APU has a hissing noise. It looks like this: DMG_hiss

Audio I uploaded some samples on my drive. It contains a plain text file that wrote some descriptions of samples. https://drive.google.com/file/d/10BwRPw2CkZUchpEji6YLlRW1o4-M2OeP/view?usp=sharing

Your environment (please complete the following information):

(and my friend has tested on Logic, same issue.)

Additional context It looks like anti-aliasing is needed.

yokemura commented 3 years ago

Thanks for your info.

Is the waves of M8P2 are rendered directly from DAWs? or any conversion or DA/AD involved?

M8P's audio rendering is no simpler than any waveform generation algorithm. Just renders -1 or 1 according to the cycle phase.

And as a matter of fact, the waveform rendered directly from DAW(mine is Logic Pro X) looks like this.

スクリーンショット 2021-05-03 8 16 25

I know this is an extreme waveform which is not likely to happen with ordinary instruments, and it's likely that converting it to any other format will cause some distortion as it's too edgy.

sdhizumi commented 3 years ago

I updated the samples: added some samples that I exported renderer on FL and Reaper. Please re-download the archive. Previous samples are recorded by the FL's internal recorder, didn't go through any DA/ADs.

Did you really check my samples? Probably you (and me) can see almost the same (but a bit different) waveform between DMG_M8P2_44.1K, 48K, and 192K samples in the M8P2 part on Audacity. It looks like this (these samples are edited to the left channel only): image

This problem is not in the waveform. I talk about the spectrum. Please listen and check my samples. You can probably recognize that each sample has the almost same waveform, but will sound different.

At-sushi commented 2 years ago

One possible case is that a margin of sampling error causes the alias noises. I don't know how Gameboy is solving this issue, but I think oversampling may solve the problem.

yokemura commented 2 years ago

We should clarify about what "aliasing" means here.

Usually in the audio domain "aliasing" means the one in the sampling theorem. That is, having an analog signal at the first place, and then trying to "sample" it in a discrete way, there will be unwanted jumps in the resulted waveform.

M8P doesn't sample any analog signal but just generate a digital waveform from scratch in pure digital way. So in this context discussing about "aliasing" doesn't make sense.

On the other hand, the word "aliasing" can be used in a different way. By analogy to the graphic domain, "aliasing" can refer to any untamed sharp digital edge (the etymology is the same though). I believe @sdhizumi is saying "aliasing" in this context. M8P's waveform is exactly that edgy, which can be beyond expectations of some conversion processes. It's understandable that it results to a distorted edge in the waveform. (So it can be said "There is an aliasing and the noise from conversion")

If it is really a problem, the solution will be applying a high-pass filter. But at the same time it will spoil M8P's sharp and crude digital sound, in other words, the sound character will change. So I have to consider carefully if I should introduce the filter or not.
At least it should be optional because the pure digital waveform is by my design, and it won't cause any problem unless you try to convert the waveform especially the sample rate. Furthermore, I believe that the issue can be avoided by applying a filter on your DAW.

nakakq commented 2 years ago

I'm also interested in this problem, so I'd like to provide some additional information.

M8P doesn't sample any analog signal but just generate a digital waveform from scratch in pure digital way. So in this context discussing about "aliasing" doesn't make sense.

It seems the M8P's oscillator produces "aliasing" in the sense of folding noise. The following is the spectrogram of the audio DMG_M8P2_48K.wav by sdhizumi. The former is GB, the latter is M8P. The M8P's output has more folding noise.

image

The implementation in Source/PulseVoice.cpp is the following:

return angle < rate * MathConstants<float>::pi ? -1.0 : 1.0;

The formulation is as same as the definition of an analog square wave (which has infinite harmonic components). Roughly speaking, the implicit harmonics above the sampleRate / 2 appear as aliases.

Suppression by low-pass/high-pass filtering is difficult because aliasing components already lie between the "true" harmonics. To suppress the aliasing of the oscillator, one simple solution is to quantize the waveform's cycle period (and the jumping points) to an integer in sample units. Perhaps this is why there's less aliasing in the GB's square wave. However, this method sacrifices pitch accuracy. Of course, there's a bunch of much more high-quality methods (e.g. additive synthesis, BLIT, Poly-BLEP, wavetable with interpolation, ...).

In my opinion, generating an anti-aliased waveform is reasonable in general. Although, I also like the fat/edgy sound of the current M8P. (I'm wondering if it's possible to add the same aliasing at any sampling rate, even 44.1 kHz or 192 kHz!)

yokemura commented 2 years ago

Thanks, @nakakq

It's an interesting discussion.

It doesn't necessarily means that it deals with sinusoidal waveform only because having the constant PI in the formula. Simply saying the above code is just checking a timer.

The synthesizer core loops a sort of timing value which ranges from 0 to a certain maximum value. And while the value is under the half of the maximum it produces -1, and then +1 for the rest. The maximum value is set to 2PI just because it's convenient when you are working with sinusoidal waves, not because there underlies* a sinusoidal wave. So in short, it is still generating -1 or 1 according to the timer, not sampling implicit analog waveform.

Saying that, it's also true that the folding happens with above algorithm. But the note height to make it happen is pretty high.

As @nakakq pointed out, at the point it exceeds the sampleRate / 2 it starts skipping the -1 / +1 sequence. Then, what is it ? In this case it is note height. Supposing the sample rate is 44.1kHz, the note frequency of sampleRate / 2 is... I can't calculate right away but it's far above C10 (which is the highest value on the table below) https://musicinformationretrieval.com/midi_conversion_table.html Reversely the folding will not happen under this frequency.

To summarize, a PI constant appears in the code but there isn't an implicit sin wave. The folding still can happen but the note height to make it happen is extremely high.

nakakq commented 2 years ago

Maybe my explanation about implicit harmonics was unclear, so let me explain in detail.

Formulation

We're going to generate a square wave (50% duty) with period P. The timer checking implementation is like this:

Then, the periodic wave y(n) can be expressed as a sum of sine waves (according to Fourier expansion):

As you can see, this formula contains infinite number of sine waves with frequency multiplied by 2h-1. These sine waves (except h = 1) are so-called overtones (or harmonics). These overtones are the matter. The overtones which exceed the sampleRate / 2 appear as aliases in a digital signal. The aliasing occurs even the note frequency is low, because of the infinite number of overtones. To prevent aliasing, we need to limit the number of overtones (so that the all of them don't exceed the sampleRate / 2).

Demonstration

I wrote a C++ program to demonstrate the aliasing effect (source code). This program outputs 3 WAV files; they are all square waves but are generated by different methods. The note frequency gradually changes from C3 (130.81 Hz) to C10 (16744.04 Hz). The sampleRate / 2 is 22050 Hz.

The 3 generation methods are:

Results (spectrogram):

image

The spectrogram of M8P2 and ManySines are very similar. This implies the M8P2 waveform has a lot of overtones which exceeds the sampleRate / 2. In contrast, Sines doesn't have aliases; because it limits the overtones to ensure the sampling theorem.

Here's a graphical explanation for the folding of the overtones:

image

To sum up, the implicit overtones make the aliasing. I hope this comment is useful!

yokemura commented 2 years ago

Thanks. I have two questions.

nakakq commented 2 years ago

Here's the original files: pulse.zip

My spectrogram settings in Audacity (the window size was 1024):

image

flubber2077 commented 2 years ago

@yokemura here is some info on reducing aliasing waves. essentially, some trickery needs to be done at the discontinuities, otherwise harmonics will reflect back down from half the sampling rate, becoming weird inharmonics. id recommend polyblep; its computationally quick and usually the fastest to code into the oscillators

yokemura commented 2 years ago

@nakakq Sorry for my long silence. I've been busy in making my music.

Before moving forward the discussion we have to think about what NOISE is, what DISTORTION is. As far as taking a glance at the waveforms, the one which has noise is "Sines" waveform. Which waveform should we respect?

スクリーンショット 2021-12-21 16 11 38

And as another point of view, we have to be aware that the analysis process can produce a noise. In short, analysis may lie. The analysis methods like spectrogram uses FFT/wavelet-based algorithm involve the process to decompose the waveform into sine waves, and it means any waveforms that is difficult to represent as a sinusoidal composition can result in an unexpected expression.

My understanding is like this:

nakakq commented 2 years ago

@yokemura Thank you! I'd like to reply line-by-line.

My opinion in a nutshell:


As far as taking a glance at the waveforms, the one which has noise is "Sines" waveform. Which waveform should we respect?

In my opinion, it's difficult to discuss the sound quality using the time-domain waveform. Indeed the m8p's waveform is cleaner than the "Sines" in the waveform, but the frequency-domain characteristics are more important for the sound.

Human hears mainly in the frequency domain (recall the cochlea of the ear), so we should evaluate them on the frequency domain. The time-domain waveform can be important for evaluating envelope or dynamics, but maybe they're not necessary for the oscillators.


And as another point of view, we have to be aware that the analysis process can produce noise. In short, analysis may lie. The analysis methods like spectrogram uses FFT/wavelet-based algorithm involve the process to decompose the waveform into sine waves, and it means any waveforms that is difficult to represent as a sinusoidal composition can result in an unexpected expression.

The PURE square waveform is too sharp to be expressed as a convolution of sine waves. That's why m8p's spectrogram shows the impurity. It must be caused by the ANALYSIS process.

I believe that such a frequency analysis is appropriate for evaluating. The reasons are the below:

You may hear the obvious aliasing in the last half of the "M8P2" audio file compared to "Sines" (in the audio samples previously sent, please be careful with the volume), or playing high-pitched notes with vibrato on m8p.

Note that the sampling theorem (which causes the aliasing) is a limitation of the digital signal itself, not a limitation of the analysis. Any digital waveforms can't represent a true square wave with infinity resolution (except for the wavelength with even samples).


The "Sine" waveform is not a square wave but something alike that can avoid the analysis noise. Or it can be said as DISTORTED when we put focus on the pure square wave.

I agree that the "Sine" is an approximated square wave, which is designed to avoid artifacts in the frequency domain. It can be viewed as a low-passed square wave with a cutoff sampleRate / 2. The ripples around the edge of the waveform are called Gibbs effect. They're a side effect of the low-pass filter.

However, I think that the m8p also generates approximated waveform; it has another kind of "distortion", which is about the pitch period. For example, assuming that the sampling rate is 48000 Hz and the OSC frequency is 720 Hz. Then the pitch period is calculated as 48000 / 720 = 66.66... samples. In this situation, the m8p outputs a square wave that rapidly switches between 66 and 67 samples/period. Also, when the period is an even number, the first and second half of the square wave are not the same length (e.g. 67 = 33 + 34).

Such switching can cause a kind of weird sound. The switching leads some false components in the frequency domain, and our ears perceive it as the aliasing noise. To improve the perceived sound, alias-free oscillators are reasonable (so many digital synthesizers use them).

But in m8p, there's maybe a room for thought considering that the chip-tune music needs some Lo-Fi sound. Sometimes aliasing makes sound powerful (e.g. synth leads), and sometimes undesirable. It's not necessary to replace m8p completely with alias-free oscillators, but it's nice to add alias-free oscillators as an option I think.


EDIT: clarified the comment, and sorry about such a long reply!