The proper way is either via the Fourier series; or look into BLIT [1] synthesis.
Anyway, what could have been the purpose for the gameboy hardware to provide both 25% and 75% duty cycle? In audio, these sound identical to a human, no? They are the same waveform with inverted polarity. They have the same overtone content.
Confirmed. Author, please fix! For example, in the first set of radio buttons:
1. Leave it at the 50% default
2. Press play
3. Change it to the 12.5% option -- we continue to hear the same sound
4. Change it back to 50% -- finally we hear a different sound
This is broken. Another example:
1. Listen to 12.5% after having come directly from 25%
2. Listen to 12.5% after having come directly from 50%
The 12.5% should sound identical in either case, but it erroneously does not.
Based on some cursory research, however, it seems that duty cycle is different than pulse width, so now I am unsure if they are trying to use duty cycle variation to implement pulse width modulation (PWM) or if they are doing something else entirely.
And while when going from 0% to 50% duty cycle it could be said that "a square wave with a low pulse width will sound thinner than one with a high pulse width", however, once you go past 50% duty cycle the situation reverses. So a 25% duty cycle would sound almost identical to a 75% duty cycle...the amplitudes of their Fourier transform components would be identical.
I'm having a tough time reconciling how the former could be almost identical while the latter is identical. I guess the former involves a human listening through a speaker which has asymmetric imperfections (maybe the speaker moves outward more easily than it moves inward, or a DC offset in the signal leads to compression in the high-excursion side that doesn't exist on the low-excursion side, etc.) whereas the FFT readout doesn't necessarily have a speaker in the system at all.
As for compression, this [0] is a good intro. Most commonly it is applied to a signal deliberately to achieve a desired outcome, but I'm referring to a (generally) undesired speaker nonlinearity [1] near its maximum power handling capacity.
[0] https://en.wikipedia.org/wiki/Dynamic_range_compression
[1] https://marshallforum.com/threads/what-exactly-does-speaker-...
Based on the gameboy wiki I looked up, the phase of the 25% duty and 75% duty are such that they are inverse of each other, seemingly eliminating the possibility of combining the two for different waveforms.
I'm not sure, but I believe the original NES Castlevania does this in some places, like in the "you died" jingle. (It's possible I'm misremembering and it's simply two square notes separated by an octave.)
Is this something to expect or is there still an error in the code?
If it was just that single wave, but there is more than 1 audio channel.
[1]: https://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware#Sq...
Depending on how that's processed downstream it could sound very different I imagine.
In crude ASCII art (two inputs mixed to an output):
---_________
---_---_---_
¨¨¨_---_---_
vs --------____
-___-___-___
¨---¨----___
Not sure how much that comes into play on the Gameboy though.
I haven't been able to find anything about why exactly they chose to provide both 25% and 75% DCs - they do sound the same minus the inverted polarity like you mentioned.
If you use a perfect square wave, the aliasing is extremely audible and sounds terrible.
As far as I can tell, in native everyone uses a nifty algorithm called bank-limited audio synthesis, and specifically blargg's implementation blip_buf.
https://slack.net/~ant/bl-synth/
If I were OP I'd try to compile the rust port of this library to WASM.
But I overlooked the point that the GP mentions that the processor source code must be loaded from a separate JS file. That's some quite annoying overhead.
The sound created by that generator passes through a fairly complicated filter known as The game boy's speaker. To properly create a game boy sound, you need to find or take an impulse response of that speaker and convolve it with the output of your oscillator.
I know all of that. But I just didn't want to get into these details in this thread.
Looking forward to seeing the tracker some time!
The other thing is that they have made cardinal sins like relying on direct form biquads for basic filters and using an array for parameters. It's good enough to make a demo but falls apart in the situations that you actually care about, and these are things that the pro audio industry (or similar, like gaming) have had solved for a very long time (*)
* pro audio software is a disaster, but for other reasons
(Chrome on mac)
you might also be interested in Glicol (https://glicol.org/)
especially this example: https://glicol.org/tour#meta2
it's rhai in rust -> wasm -> sab -> audioworklet
So just PWM?
Also, if you needed to exceed 50%, you could have just combined two different square waves, out of phase, and you'd be there.
I don't think this is possible. A balanced square wave has no even harmonics in the frequency domain. Anytime the duty cycle is not 0%, 50%, or 100%, you will have non-zero even harmonics.
A linear combination (scaled sum or difference) of two balanced square waves will necessarily still have all even harmonics at zero, and thus cannot emulate a square wave with a duty cycle different from 50%.
The article is specifically trying to achieve those harmonics. This is the initial problem.
> A linear combination (scaled sum or difference) of two balanced square waves will necessarily still have all even harmonics at zero
Yea, that's why you need the phase offset, as I mentioned.
> and thus cannot emulate a square wave with a duty cycle different from 50%.
You can /synthesize/ a square wave at any duty cycle you like. We're still doing Fourier just without the whole transform.
Is this something to expect or is there an error in the code?