The git repo at GitHub - itsjoesullivan/dx7-patches: JSON representation of a collection of Yamaha DX7 patches compiled by Glenn Scott has a bunch of dx7 patches in json format.
I created a script to convert the json formatted patches into FM7 patches. The end result is not quite an accurate dx7 emulator taken to the nth degree like GitHub - everythingwillbetakenaway/DX7-Supercollider: My accurate Yamaha DX-7 clone. Programmed in Supercollider. - though I did steal some of the lookup tables from there. There are a number of things not implemented like velocity and osc sync and some things are simplified or naive or maybe entirely wrong. It’s nowhere near as complete and accurate as Dexed or DX7-Supercollider but still able to produce some nice sounding signals I think. Anyway, there’s some room for improvement.
To use:
# Install the JSONlib quark: https://github.com/musikinformatik/JSONlib
# Clone the dx7-patches repo or copy a json file and save it locally, e.g. elec-pno-a.json.
# Copy the script below and save it locally, e.g. "fm7.scd".
In SC load the script. It will return a function:
~func = "fm7.scd".loadRelative
The function then can be used to convert a json file in the dx7-patches format. The result will be another function which takes a freq and a gate as arguments and returns the configured FM7 signal. So it is necessary to execute it in a SynthDef.
(
var sigFunc = ~func.("elec-pno-a.json".resolveRelative);
SynthDef(\fm7, {
var freq = \freq.kr;
var gate = \gate.kr(1);
var sig = sigFunc.(freq, gate);
var env = Env.asr.kr(gate: gate, doneAction:Done.freeSelf);
sig = Splay.ar(sig) * env * -12.dbamp;
Out.ar(\out.kr(0), sig);
}).add
);
x = Synth(\fm7, [\freq, 64.midicps, \gate, 1]);
x.set(\gate, 0)
Here is the script. Copy and save locally.
(
/*
look up tables copied from https://github.com/everythingwillbetakenaway/DX7-Supercollider/blob/master/DX7.scd
*/
var ratesTable = [
39.638000, 37.013000, 34.388000, 31.763000, 27.210500,
22.658000, 20.408000, 18.158000, 15.908000, 14.557000,
13.206000, 12.108333, 11.010667, 9.913000, 8.921000,
7.929000, 7.171333, 6.413667, 5.656000, 5.307000,
4.958000, 4.405667, 3.853333, 3.301000, 2.889000,
2.477000, 2.313000, 2.149000, 1.985000, 1.700500,
1.416000, 1.274333, 1.132667, 0.991000, 0.909000,
0.827000, 0.758000, 0.689000, 0.620000, 0.558000,
0.496000, 0.448667, 0.401333, 0.354000, 0.332000,
0.310000, 0.275667, 0.241333, 0.207000, 0.180950,
0.154900, 0.144567, 0.134233, 0.123900, 0.106200,
0.088500, 0.079667, 0.070833, 0.062000, 0.056800,
0.051600, 0.047300, 0.043000, 0.038700, 0.034800,
0.030900, 0.028000, 0.025100, 0.022200, 0.020815,
0.019430, 0.017237, 0.015043, 0.012850, 0.011230,
0.009610, 0.009077, 0.008543, 0.008010, 0.006960,
0.005910, 0.005357, 0.004803, 0.004250, 0.003960,
0.003670, 0.003310, 0.002950, 0.002590, 0.002420,
0.002250, 0.002000, 0.001749, 0.001499, 0.001443,
0.001387, 0.001242, 0.001096, 0.000951, 0.000815,
0.000815, 0.000815, 0.000815, 0.000815, 0.000815,
0.000815, 0.000815, 0.000815, 0.000815, 0.000815,
0.000815, 0.000815, 0.000815, 0.000815, 0.000815,
0.000815, 0.000815, 0.000815, 0.000815, 0.000815,
0.000815, 0.000815, 0.000815, 0.000815, 0.000815,
0.000815, 0.000815, 0.000815
];
var fbtable = [0, 0.0066, 0.0077, 0.011, 0.0153, 0.018, 0.0226, 0.063];
var pitchTable = [ //128
-48.000000, -43.497081, -38.995993, -35.626132, -31.873615,
-28.495880, -25.500672, -22.872620, -20.998167, -19.496961,
-18.373238, -17.251065, -16.122139, -15.375956, -14.624487,
-13.876516, -13.126351, -12.375000, -12.000000, -11.625000,
-11.250000, -10.875000, -10.500000, -10.125000, -9.750000,
-9.375000, -9.000000, -8.625000, -8.250000, -7.875000,
-7.500000, -7.125000, -6.750000, -6.375000, -6.000000,
-5.625000, -5.250000, -4.875000, -4.500000, -4.125000,
-3.750000, -3.375000, -3.000000, -2.625000, -2.250000,
-1.875000, -1.500000, -1.125000, -0.750000, -0.375000, 0.000000,
0.375000, 0.750000, 1.125000, 1.500000, 1.875000, 2.250000,
2.625000, 3.000000, 3.375000, 3.750000, 4.125000, 4.500000,
4.875000, 5.250000, 5.625000, 6.000000, 6.375000, 6.750000,
7.125000, 7.500000, 7.875000, 8.250000, 8.625000, 9.000000,
9.375000, 9.750000, 10.125000, 10.500000, 10.875000, 11.250000,
11.625000, 12.000000, 12.375000, 12.750000, 13.125000,
14.251187, 15.001922, 16.126327, 17.250917, 18.375718,
19.877643, 21.753528, 24.373913, 27.378021, 30.748956,
34.499234, 38.627888, 43.122335, 47.624065, 48.0, 48.0, 48.0,
48.0, 48.0, 48.0, 48.0, 48.0, 48.0, 48.0, 48.0, 48.0, 48.0,
48.0, 48.0, 48.0, 48.0, 48.0, 48.0, 48.0, 48.0, 48.0, 48.0,
48.0, 48.0, 48.0, 48.0, 48.0
];
var lfoFreqTable = [
0.062506, 0.124815, 0.311474, 0.435381, 0.619784,
0.744396, 0.930495, 1.116390, 1.284220, 1.496880,
1.567830, 1.738994, 1.910158, 2.081322, 2.252486,
2.423650, 2.580668, 2.737686, 2.894704, 3.051722,
3.208740, 3.366820, 3.524900, 3.682980, 3.841060,
3.999140, 4.159420, 4.319700, 4.479980, 4.640260,
4.800540, 4.953584, 5.106628, 5.259672, 5.412716,
5.565760, 5.724918, 5.884076, 6.043234, 6.202392,
6.361550, 6.520044, 6.678538, 6.837032, 6.995526,
7.154020, 7.300500, 7.446980, 7.593460, 7.739940,
7.886420, 8.020588, 8.154756, 8.288924, 8.423092,
8.557260, 8.712624, 8.867988, 9.023352, 9.178716,
9.334080, 9.669644, 10.005208, 10.340772, 10.676336,
11.011900, 11.963680, 12.915460, 13.867240, 14.819020,
15.770800, 16.640240, 17.509680, 18.379120, 19.248560,
20.118000, 21.040700, 21.963400, 22.886100, 23.808800,
24.731500, 25.759740, 26.787980, 27.816220, 28.844460,
29.872700, 31.228200, 32.583700, 33.939200, 35.294700,
36.650200, 37.812480, 38.974760, 40.137040, 41.299320,
42.461600, 43.639800, 44.818000, 45.996200, 47.174400,
47.174400, 47.174400, 47.174400, 47.174400, 47.174400,
47.174400, 47.174400, 47.174400, 47.174400, 47.174400,
47.174400, 47.174400, 47.174400, 47.174400, 47.174400,
47.174400, 47.174400, 47.174400, 47.174400, 47.174400,
47.174400, 47.174400, 47.174400, 47.174400, 47.174400,
47.174400, 47.174400, 47.174400
];
var algos = [
// algo 1
[{|fb=1| [ [0,1,0,0,0,0], [0,0,0,0,0,0], [0,0,0,1,0,0], [0,0,0,0,1,0], [0,0,0,0,0,1], [0,0,0,0,0,fb] ] }, [0, 2]],
// algo 2
[{|fb=1| [ [0,1,0,0,0,0], [0,fb,0,0,0,0], [0,0,0,1,0,0], [0,0,0,0,1,0], [0,0,0,0,0,1], [0,0,0,0,0,0] ] }, [0, 2]],
// algo 3
[{|fb=1| [ [0,1,0,0,0,0], [0,0,1,0,0,0], [0,0,0,0,0,0], [0,0,0,0,1,0], [0,0,0,0,0,1], [0,0,0,0,0,fb] ] }, [0, 3]],
// algo 4
[{|fb=1| [ [0,1,0,0,0,0], [0,0,1,0,0,0], [0,0,0,0,0,0], [0,0,0,0,1,0], [0,0,0,0,0,1], [0,0,0,fb,0,0] ] }, [0, 3]],
// algo 5
[{|fb=1| [ [0,1,0,0,0,0], [0,0,0,0,0,0], [0,0,0,1,0,0], [0,0,0,0,0,0], [0,0,0,0,0,1], [0,0,0,0,0,fb] ] }, [0, 2, 4]],
// algo 6
[{|fb=1| [ [0,1,0,0,0,0], [0,0,0,0,0,0], [0,0,0,1,0,0], [0,0,0,0,0,0], [0,0,0,0,0,1], [0,0,0,0,fb,0] ] }, [0, 2, 4]],
// algo 7
[{|fb=1| [ [0,1,0,0,0,0], [0,0,0,0,0,0], [0,0,0,1,1,0], [0,0,0,0,0,0], [0,0,0,0,0,1], [0,0,0,0,0,fb] ] }, [0, 2]],
// algo 8
[{|fb=1| [ [0,1,0,0,0,0], [0,0,0,0,0,0], [0,0,0,1,1,0], [0,0,0,fb,0,0], [0,0,0,0,0,1], [0,0,0,0,0,0] ] }, [0, 2]],
// algo 9
[{|fb=1| [ [0,1,0,0,0,0], [0,fb,0,0,0,0], [0,0,0,1,1,0], [0,0,0,0,0,0], [0,0,0,0,0,1], [0,0,0,0,0,0] ] }, [0, 2] ],
// algo 10
[{|fb=1| [ [0,1,0,0,0,0], [0,0,1,0,0,0], [0,0,fb,0,0,0], [0,0,0,0,1,1], [0,0,0,0,0,0], [0,0,0,0,0,0] ] }, [0, 3]],
// algo 11
[{|fb=1| [ [0,1,0,0,0,0], [0,0,1,0,0,0], [0,0,0,0,0,0], [0,0,0,0,1,1], [0,0,0,0,0,0], [0,0,0,0,0,fb] ] }, [0, 3]],
// algo 12
[{|fb=1| [ [0,1,0,0,0,0], [0,fb,0,0,0,0], [0,0,0,1,1,1], [0,0,0,0,0,0], [0,0,0,0,0,0], [0,0,0,0,0,0] ] }, [0, 2]],
// algo 13
[{|fb=1| [ [0,1,0,0,0, 0], [0,0,0,0,0,0], [0,0,0,1,1, 1], [0,0,0,0,0, 0], [0,0,0,0,0, 0], [0,0,0,0,0, fb] ] }, [0, 2]],
// algo 14
[{|fb=1| [ [0,1,0,0,0, 0], [0,0,0,0,0,0], [0,0,0,1,0, 1], [0,0,0,0,1, 1], [0,0,0,0,0, 0], [0,0,0,0,0, fb] ] }, [0, 2] ],
// algo 15
[{|fb=1| [ [0,1,0,0,0, 0], [0,fb,0,0,0,0], [0,0,0,1,0, 0], [0,0,0,0,1, 1], [0,0,0,0,0, 0], [0,0,0,0,0, 0] ] }, [0, 2]],
// algo 16
[{|fb=1| [ [0,1,1,0,1, 0], [0,0,0,0,0,0], [0,0,0,1,0, 0], [0,0,0,0,0, 0], [0,0,0,0,0, 1], [0,0,0,0,0, fb] ] }, [0]],
// algo 17
[{|fb=1| [ [0,1,1,0,1, 0], [0,fb,0,0,0,0], [0,0,0,1,0, 0], [0,0,0,0,0, 0], [0,0,0,0,0, 1], [0,0,0,0,0, 0] ] }, [0]],
// algo 18
[{|fb=1| [ [0,1,1,1,0, 0], [0,0,0,0,0, 0], [0,0,fb,0,0, 0], [0,0,0,0,1, 0], [0,0,0,0,0, 1], [0,0,0,0,0, 0] ] }, [0]],
// algo 19
[{|fb=1| [ [0,1,0,0,0, 0], [0,0,1,0,0, 0], [0,0,0,0,0, 0], [0,0,0,0,1, 0], [0,0,0,0,0, 1], [0,0,0,0,0, fb] ] }, [0, 3, 4] ],
// algo 20
[{|fb=1| [ [0,0,1,0,0, 0], [0,0,1,0,0, 0], [0,0,fb,0,0, 0], [0,0,0,0,1, 1], [0,0,0,0,0, 0], [0,0,0,0,0, 0] ] }, [0, 1, 3] ],
// algo 21
[{|fb=1| [ [0,0,1,0,0, 0], [0,0,1,0,0, 0], [0,0,fb,0,0, 0], [0,0,0,0,1, 0], [0,0,0,0,0, 1], [0,0,0,0,0, 0] ] }, [0, 1, 3, 4] ],
// algo 22
[{|fb=1| [ [0,1,0,0,0, 0], [0,0,0,0,0, 0], [0,0,0,0,0, 1], [0,0,0,0,0, 1], [0,0,0,0,0, 1], [0,0,0,0,0, fb] ] }, [0, 2, 3, 4] ],
// algo 23
[{|fb=1| [ [0,0,0,0,0, 0], [0,0,1,0,0, 0], [0,0,0,0,0, 0], [0,0,0,0,0, 1], [0,0,0,0,0, 1], [0,0,0,0,0, fb] ] }, [0, 1, 3, 4]],
// algo 24
[{|fb=1| [ [0,0,0,0,0, 0], [0,0,0,0,0, 0], [0,0,0,0,0, 1], [0,0,0,0,0, 1], [0,0,0,0,0, 1], [0,0,0,0,0, fb] ] }, [0, 1, 2, 3, 4] ],
// algo 25
[{|fb=1| [ [0,0,0,0,0, 0], [0,0,0,0,0, 0], [0,0,0,0,0, 0], [0,0,0,0,0, 1], [0,0,0,0,0, 1], [0,0,0,0,0, fb] ] }, [0, 1, 2, 3, 4] ],
// algo 26
[{|fb=1| [ [0,0,0,0,0, 0], [0,0,1,0,0, 0], [0,0,0,0,0, 0], [0,0,0,0,1, 1], [0,0,0,0,0, 0], [0,0,0,0,0, fb] ] }, [0, 1, 3] ],
// algo 27
[{|fb=1| [ [0,0,0,0,0, 0], [0,0,1,0,0, 0], [0,0,fb, 0,0, 0], [0,0,0,0,1, 1], [0,0,0,0,0, 0], [0,0,0,0,0, 0] ] }, [0, 1, 3] ],
// algo 28
[{|fb=1| [ [0,1,0,0,0, 0], [0,0,0,0,0, 0], [0,0,0,1,0, 0],[0,0,0,0,1, 0], [0,0,0,0,fb,0], [0,0,0,0,0, 0] ] }, [0, 2, 5] ],
// algo 29
[{|fb=1| [ [0,0,0,0,0, 0], [0,0,0,0,0, 0], [0,0,0,1,0, 0], [0,0,0,0,0, 0], [0,0,0,0,0, 1], [0,0,0,0,0, fb] ] }, [0, 1, 2, 4] ],
// algo 30
[{|fb=1| [ [0,0,0,0,0, 0], [0,0,0,0,0, 0], [0,0,0,1,0, 0], [0,0,0,0,1, 0], [0,0,0,0,fb,0], [0,0,0,0,0, 0] ] }, [0, 1, 2, 5] ],
// algo 31
[{|fb=1| [ [0,0,0,0,0, 0], [0,0,0,0,0, 0], [0,0,0,0,0, 0], [0,0,0,0,0, 0], [0,0,0,0,0, 1], [0,0,0,0,0, fb] ] }, [0, 1, 2, 3, 4] ],
// algo 32
[{|fb=1| [ [0,0,0,0,0, 0], [0,0,0,0,0, 0], [0,0,0,0,0, 0], [0,0,0,0,0, 0], [0,0,0,0,0, 0], [0,0,0,0,0, fb] ] }, [0, 1, 2, 3, 4, 5]]
];
var convert = {|path|
var obj = ();
var operators;
var cent2ratio, eg2env, pitchEg2env;
var dict, file, json, curve2Int;
file = File(path, "r");
json = file.readAllString;
dict = JSONlib.convertToSC(json);
file.close;
cent2ratio = {|cent|
var result = cent/100/12;
result = 2.pow(result);
result = round(result * 1000000);
result = result/1000000;
};
eg2env = {|obj|
var levels = [ 'level4', 'level1', 'level2', 'level3', 'level4' ].collect({|v|
var level = obj[v];
level = level.lincurve(0, 100, 0, 1, 0);
level;
});
var rates = ['rate1', 'rate2', 'rate3', 'rate4'].collect({|v|
var rate = obj[v];
rate = ratesTable[rate];
rate
});
[levels, rates];
};
pitchEg2env = {|obj|
var levels = [ 'level4', 'level1', 'level2', 'level3', 'level4' ].collect({|v|
var level = obj[v];
level = pitchTable[level];
level;
});
var rates = ['rate1', 'rate2', 'rate3', 'rate4'].collect({|v, i|
var rate = obj[v];
rate = rate.lincurve(0, 99, 0.15, 0.002, -1);
rate
});
[levels, rates] //.debug("pitchlevelrates");
};
curve2Int = {|val|
if (val[0] == $+) { 3 } { -3 };
};
obj['name'] = dict['name'].stripWhiteSpace.select({|v| v.isAlphaNum });
obj['algo'] = dict['algorithm'];
obj['fb'] = dict['feedback'];//7;// * 0.6;
obj['pitchEnv'] = pitchEg2env.(dict['pitchEG']);
obj['lfo'] = dict['lfo'];
operators = dict['operators'].collect({|op|
var obj = ();
var freq = op['frequency'];
var fixed = (op['oscillatorMode'] == "fixed");
var detune = cent2ratio.(op['detune']);
var eg = op['eg'];
var atkcurve = op['keyboardLevelScaling']['leftCurve'];
var relcurve = op['keyboardLevelScaling']['rightCurve'];
var level = op['outputLevel'];
var env = eg2env.(eg);
obj['freq'] = freq.max(0.5);
obj['fixedfreq'] = fixed;
obj['detune'] = detune;
obj['levels'] = env[0];
obj['times'] = env[1];
obj['amp'] = level.linlin(0, 99, 0, 1);
obj['atkCurve'] = curve2Int.(atkcurve);
obj['relCurve'] = curve2Int.(relcurve);
obj
});
obj['operators'] = operators;
obj;
};
var build = {|obj|
var synth = {|freq, gate|
var ops, mods;
var sig;
var algo = obj['algo'];
var fb = obj['fb'];
var pitchEnv = obj['pitchEnv'];
var lfo, speed, pitchDepth, amDepth, delay, wave, which;
freq = freq.asArray[0];
speed = lfoFreqTable[ obj['lfo']['speed'] ];
pitchDepth = obj['lfo']['pitchModDepth'];
pitchDepth = pitchDepth.lincurve(0, 99, 0.01, 0.27, 4);
amDepth = obj['lfo']['amDepth'];
amDepth = amDepth.lincurve(0, 99, 0, 1, 4);
delay = obj['lfo']['delay'].linlin(0, 99, 0.01, 3.4);
wave = obj['lfo']['wave'].stripWhiteSpace.toLower;
wave = ['sine', 'triangle', 'sawtooth up', 'sawtooth down', 'square'].indexOf( wave.asSymbol ) ?? 0;
which = \lfoWave.kr(wave, spec: ControlSpec(0, 4, \lin, 1, wave, units:"fm7"));
// not sure if this should actually be per voice - probalby not
lfo = Select.kr(which, [
SinOsc.kr(speed), //sine
LFTri.kr(speed), // triangle
LFSaw.kr(speed, 1), // sawtooth up
LFSaw.kr(speed.neg, 1), // sawtooth down
LFPulse.kr(speed) // pulse
]);
lfo = lfo * Env.dadsr(delayTime: delay, attackTime: 0.01, decayTime: 0, sustainLevel: 1, releaseTime: 0.01).kr(gate:gate);
freq = freq * lfo.linlin(-1, 1, pitchDepth.neg, pitchDepth).midiratio;
freq = freq * Env(pitchEnv[0].midiratio, pitchEnv[1] ).kr(gate:gate);
ops = obj.operators.collect({|v|
var myfreq;
var opfreq = v['freq'];
var isfixed = v['fixedfreq'];
var levels = v['levels'];
var times = v['times'];
var detune = v['detune'];
var amp = v['amp'];
var atkcurve = v['atkCurve'];
var relcurve = v['relCurve'];
if (isfixed) {
myfreq = opfreq;
}{
myfreq = freq * opfreq;
};
myfreq = myfreq * detune;
[myfreq, 0, Env(levels, times, curve: [atkcurve, 0, 0, relcurve], releaseNode:3).ar(gate: gate) * amp]
});
algo = algos[algo.debug("algo")-1];
mods = algo[0].( fbtable[fb] * fb );
sig = FM7.ar(ops, mods).slice( algo[1] ).asArray.sum;
sig = sig * 2.pow ( lfo * amDepth );
sig * -6.dbamp
};
synth;
};
var func = {|path|
var obj = convert.(path);
var synth = build.(obj);
synth;
};
func;
)
It’s not cutting edge sounds but it’s nice to have some nice sounds handy that are relatively easy to create.