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