I'm trying to implement some features of a Yamaha YM3812 sound chip (aka OPL2 http://en.wikipedia.org/wiki/YM3812) in JavaScript using Audiolet (a synthesis library, http://oampo.github.io/Audiolet/api.html)
Audiolet allows you to build a synthesizer as a graph of nodes (oscillators, DSPs, envelope generators etc).
The OPL2 has nine channels with two operators (oscillators) each. Usually, one oscillator in each channel modulates the frequency of the other. To simulate this, I've built up a chain of nodes for each channel:
Synth node chain (one of nine channels)
Node chain creation and connection code:
var FmChannel = function(audiolet) { this.car = new ModifiedSine(audiolet); this.carMult = 1; this.setCarrierWaveform(this.SIN); this.mod = new ModifiedSine(audiolet); this.modMult = 1; this.setModulatorWaveform(this.SIN); this.modMulAdd = new MulAdd(audiolet); this.carGain = new Gain(audiolet); this.carEnv = new ADSREnvelope(audiolet, 0, 0.1, 0.1, 0.1, 0.1, function() { this.carEnv.reset(); }.bind(this) ); this.carAtten = new Multiply(audiolet); this.modGain = new Gain(audiolet); this.modEnv = new ADSREnvelope(audiolet, 0, 0.1, 0.1, 0.1, 0.1, function() { this.modEnv.reset(); }.bind(this) ); this.modAtten = new Multiply(audiolet); this.modEnv.connect(this.modGain, 0, 1); this.mod.connect(this.modGain); this.modGain.connect(this.modAtten); this.modAtten.connect(this.modMulAdd); this.modMulAdd.connect(this.car); this.carEnv.connect(this.carGain, 0, 1); this.car.connect(this.carGain); this.carGain.connect(this.carAtten); // connect carAtten to the mixer from outside };
However, when I set the parameters of the modulator and carrier nodes (oscillator waveforms, relative frequencies, attenuation, ADSR parameters) and trigger notes, the output bears very little resemblance to a decent OPL2 emulator with approximately the same parameters. Some sounds are in the ballpark. Others are fairly unpleasant.
I have some ideas on how to proceed (I guess plotting the output at different stages would be a good starting point), but I'm hoping someone experienced can point me in the right direction, or point out something obviously wrong with what I'm doing. I don't have a signal-processing or strong mathematical background. I don't have a deep intuitive understanding of FM.
Some issues I suspect are:
1) My FM implementation (as shown above) is fundamentally wrong. Also, there may be an issue in the function where play a note (set the oscillator frequencies, and scale and offset the modulator before triggering the ADSR envelopes):
FmChannel.prototype.noteOn = function (frq) { var Fc = frq*this.carMult; this.car.reset(Fc); this.mod.reset(frq*this.modMult); // scale and offset modulator from range (-1, 1) to (0, 2*Fc) // (scale and offset is after ADSR gain and fixed attenuation is applied) this.modMulAdd.mul.setValue(Fc); this.modMulAdd.add.setValue(Fc); this.carEnv.reset(); this.modEnv.reset(); this.carEnv.gate.setValue(1); Thethis.modEnv.gate.setValue(1); };
2) Output of FM synths may be highly sensitive to small differences in the shape of the modulator ADSR envelope (please tell me if this is true!), and my ADSR envelopes are crude approximations at best of the ADSRs in a real OPL2. My implementation is also missing some features which seem relatively unimportant (eg key scaling), but which may affect the sound of an FM synth significantly (again, I'm not sure).
Simply put, FM synthesis uses one signal called, the “modulator” to modulate the pitch of another signal, the “carrier”, that's in the same or a similar audio range. This creates brand new frequency information in the resulting sound, changing the timbre without the use of filters.
FM synthesis is extremely good, however, at creating sounds that are difficult to achieve with subtractive synthesizers—sounds such as bell timbres, metallic tones, and the tine tones of electric pianos. Another strength of FM synthesis is punchy bass and synthetic brass sounds.
Speed that LFO up to audio rate and you've got what's called Frequency Modulation Synthesis, or FM for short. Although used in digital synths like the Yamaha DX7 since the 1980's, FM synthesis has been a part of modular technique ever since the creation of VCOs.
Most synthesizers labled 'FM' in fact do phase modulation (PM, see https://en.wikipedia.org/wiki/Phase_modulation ). There are some benefits (mostly leading to more stable sound over a large tonal range). The OPL2 may use this too, I found no clear evidence, but the Wikipedia article also uses the term 'phase modulation'.
For short, many musical synthesizers labeled 'FM' in fact featured 'PM', so you might try go with that, and check if it better fits the expected OPL2 sounds.
From a quick glance to the Audiolet source, I would guess the Sine
oscilator is doing true FM, so you may need to replace it and add a phase input to allow phase modulation.
Basically, the line
output.samples[0] = Math.sin(this.phase);
used by Sine
of the carrier oscilator would have to read something like
output.samples[0] = Math.sin(this.phase+phase_offset);
with phase_offset
controlled by the mod oscilator instead of the frequency.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With