Just fixed a bug when having more than 16 parameters, therefore we have a new bug-fix release 0.4.1.
For funs sake, here is a DX7 op implementation using DynGen showing off the efficency of the transpiler using meta programming and also how to iterate over an array of oscillators by laying them out in dyngen memory.
There are still some things not implemented from the original DX7, but for me it is close enough.
/*
DX7 op via dyngen. For resources see
https://www.righto.com/2021/12/yamaha-dx7-chip-reverse-engineering.html
and the patent https://patents.google.com/patent/US4554857A
*/
(
Ndef(\dx7, {
var controls = 6.collect({|i|
[
"mod%".format(i), "mDepth%".format(i).asSymbol.kr(1.0) * Env.perc(\attack.kr(0.01), \release.kr(0.2), curve: \curve.kr(-4.0)).kr(gate: Impulse.kr(\speed.kr(1.0), i/6)),
"freqFrac%".format(i), "freqFrac%".format(i).asSymbol.kr(1.0, spec: [0.5, 10.0, \exp]),
];
}).flatten;
DynGen.ar(
numOutputs: 1,
script: DynGenDef(\dx7).compile(#{|e|
var numOps = 6;
~numOps = numOps;
// define mem offsets
~opConfigs = 0;
~phases = ~numOps;
~sigs = ~phases + ~numOps;
~modDepths = ~sigs + ~numOps;
~freqFractions = ~modDepths + ~numOps;
// algorithm 8
// 0xABC - byte A is selector, B is accum config, C is COM
/*
~opConfigs[5] = 0x100;
~opConfigs[4] = 0x510;
~opConfigs[3] = 0x271;
~opConfigs[2] = 0x010;
~opConfigs[1] = 0x121;
~opConfigs[0] = 0x030;
*/
// algorithm 1 - also update outputs at the bottom if necessary
// see https://www.righto.com/2021/12/yamaha-dx7-chip-reverse-engineering.html#fn:alg-table
// for further algorithms
~opConfigs[5] = 0x140;
~opConfigs[4] = 0x100;
~opConfigs[3] = 0x101;
~opConfigs[2] = 0x010;
~opConfigs[1] = 0x121;
~opConfigs[0] = 0x530;
// map control parameters to internal array
// using meta programming ;)
numOps.collect({|i|
e["modDepths"][i] = ~p["mod%".format(i)];
e["freqFractions"][i] = ~p["freqFrac%".format(i)];
});
~inc = ~p.freq(100.0, spec: \freq) / ~srate;
~numOp = 5;
~numOps.loop({
~opSelector = ~opConfigs[~numOp] >> 8;
// switches
~a = (~opConfigs[~numOp] >> 5) & 0x1; // ~b = ~a
~c = (~opConfigs[~numOp] >> 6) & 0x1;
~d = (~opConfigs[~numOp] >> 7) & 0x1;
// what does this do?
~com = ~opConfigs[~numOp] & 0xff;
// calculate signal
~phases[~numOp] = (~phases[~numOp] + (~inc * ~freqFractions[~numOp])).wrap(0.0, 1.0);
~modPhase = (~phases[~numOp] + (~modIn * ~modDepths[~numOp]));
~sigs[~numOp] = (~modPhase * 2 * pi).sin;
~lastOp = ~sigs[~numOp];
// update registers
// transpiler requires an assignment for every op...
~fbDummy = ~a.ifTrue({
~fbRegisterB = ~fbRegisterA;
~fbRegisterA = ~lastOp;
});
~adder = (~c * ~memRegister) + (~d * ~lastOp);
~memRegister = ~adder;
// calculate next modulator via selector
// bug in dyngen transpiler - order of ops makes a difference!
// 0x0 is no mod - so we can skip that ;)
~modIn = ((~opSelector == 0x1) * ~lastOp) +
((~opSelector == 0x2) * ~adder) +
((~opSelector == 0x3) * ~memRegister) +
((~opSelector == 0x4) * ~fbRegisterA) +
((~opSelector == 0x5) * (~fbRegisterA + ~fbRegisterB)/2);
~numOp = ~numOp - 1;
});
// i think this should be derived from the com bits^^
~out0 = (~sigs[0] + ~sigs[2]) / 2;
}).send,
params: controls ++ [\freq, \freq.kr(100.0)],
).dup;
}).play.gui
)
This actually also implements the proper feedback algorithm, which the FM7 UGen does not ![]()