39 Stimmen

Was stimmt nicht mit diesem einfachen FM-Synthesizer-Design?

Ich versuche, einige Funktionen eines Yamaha YM3812-Soundchips (alias OPL2 http://en.wikipedia.org/wiki/YM3812) in JavaScript mithilfe von Audiolet (einer Synthesizer-Bibliothek, http://oampo.github.io/Audiolet/api.html) zu implementieren

Audiolet ermöglicht es Ihnen, einen Synthesizer als Graph von Knoten (Oszillatoren, DSPs, Hüllkurvengeneratoren usw.) aufzubauen.

Der OPL2 verfügt über neun Kanäle mit jeweils zwei Operatoren (Oszillatoren). Normalerweise moduliert ein Oszillator in jedem Kanal die Frequenz des anderen. Um dies zu simulieren, habe ich für jeden Kanal eine Kette von Knoten aufgebaut:

Synth-Knotenkette (einer von neun Kanälen)

OPL2-Kanal wie implementiert

Knotenketten-Erstellungs- und -Verbindungsscode:

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);
    // carAtten mit dem Mixer von außen verbinden
};

Wenn ich jedoch die Parameter der Modulator- und Trägernoten (Oszillator-Wellenformen, relative Frequenzen, Dämpfungen, ADSR-Parameter) einstelle und Noten auslöse, ähnelt der Ausgang nur sehr wenig einem anständigen OPL2-Emulator mit ungefähr denselben Parametern. Einige Klänge sind in etwa richtig. Andere sind ziemlich unangenehm.

Ich habe einige Ideen, wie ich vorgehen könnte (ich nehme an, das Plotten des Ausgangs an verschiedenen Stellen wäre ein guter Ausgangspunkt), aber ich hoffe, dass mir jemand mit Erfahrung den richtigen Weg weisen kann oder offensichtliche Fehler in dem aufzeigen kann, was ich tue. Ich habe keine Erfahrung im Signalprozessing oder in der Mathematik. Ich habe kein tiefes intuitives Verständnis von FM.

Einige Probleme, von denen ich glaube, dass sie vorliegen:

1) Meine FM-Implementierung (wie oben gezeigt) ist im Grunde falsch. Außerdem kann es ein Problem in der Funktion geben, in der ich eine Note spiele (die Oszillatorfrequenzen einstelle und den Modulator vor dem Auslösen der ADSR-Hüllkurven skalieren und verschieben):

FmChannel.prototype.noteOn = function (frq) {
    var Fc = frq*this.carMult;
    this.car.reset(Fc);
    this.mod.reset(frq*this.modMult);
    // Skalierung und Verschiebung des Modulators von Bereich (-1, 1) auf (0, 2*Fc)
    // (Skalierung und Verschiebung erfolgt nach Anwendung von ADSR-Gewinn und fester Dämpfung)
    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) Der Ausgang von FM-Synthesizern kann äußerst empfindlich auf kleine Unterschiede in der Form der Modulator-ADSR-Hüllkurve reagieren (sagen Sie mir bitte, ob das stimmt!), und meine ADSR-Hüllkurven sind bestenfalls grobe Annäherungen an die ADSRs in einem echten OPL2. Meine Implementierung fehlt auch einige Funktionen, die relativ unwichtig erscheinen (z. B. Tonskalierung), aber den Klang eines FM-Synthesizers erheblich beeinflussen können (auch hier bin ich mir nicht sicher).

2voto

dronus Punkte 9900

Die meisten Synthesizer, die als 'FM' bezeichnet werden, verwenden tatsächlich Phasenmodulation (PM, siehe https://en.wikipedia.org/wiki/Phase_modulation). Es gibt einige Vorteile (meistens führt dies zu einem stabileren Klang über einen großen Tonbereich).

Auch der OPL2 könnte dies verwenden, ich fand keine klaren Beweise, aber der Wikipedia-Artikel verwendet ebenfalls den Begriff 'Phasenmodulation'.

Kurz gesagt, viele musikalische Synthesizer, die als 'FM' bezeichnet werden, verwenden tatsächlich 'PM', also könnten Sie versuchen, das zu verwenden und prüfen, ob es besser zum erwarteten Klang des OPL2 passt.

Bei einem kurzen Blick auf den Audiolet-Quellcode würde ich davon ausgehen, dass der Sine-Oszillator echtes FM durchführt, sodass Sie ihn möglicherweise ersetzen und einen Phaseneingang hinzufügen müssen, um Phasenmodulation zu ermöglichen.

Grundsätzlich müsste die Zeile

output.samples[0] = Math.sin(this.phase);

die vom Sine-Trägeroszillator verwendet wird, ungefähr wie folgt gelesen werden:

output.samples[0] = Math.sin(this.phase+phase_offset);

mit phase_offset, das vom Modulationsoszillator gesteuert wird und nicht von der Frequenz.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X