EzDevInfo.com

Audiolet

A JavaScript library for real-time audio synthesis and composition from within the browser Audiolet - JavaScript library for audio synthesis and composition a javascript library for real-time audio synthesis and composition from within the browser

What's wrong with this simple FM synth design?

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)

OPL2 channel as implemented

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).


Source: (StackOverflow)