The example below plots the spectrum of a random generated 64-QAM modulation:
var options = {
appendTo : "spectrum",
canvasWidth : 800,
canvasHeight : 800/Math.sqrt(2),
drawYAxis : 3,
drawYAxisNumbers : true,
drawXAxis : 3,
drawXAxisNumbers : true,
drawGrid : true,
showValues : false,
drawTitle : true,
title : "64-QAM spectrum",
drawXAxisTitle : true,
xAxisTitle : "Discrete frequency (f)"
};
var graph = new Graph(options);
var N = 1024, // Buffer length
buffer = new Array(N);
var freq = 4000, // Analog signal frequency
Ts = 0.001; // Symbol time
var sr = 16000; // Sample rate
var updateInterval = 0.025; // In seconds
var Sx = [], // Spectrum x axis points
x = new Array(512), // Buffer x axis points
Sy = new Array(512), // Spectrum y axis points
F; // Stores the resulting FFT
for (var j = 0; j < N; j++) {
buffer[j] = 0;
x[j] = j;
Sx[j] = j/N;
}
function getSymbol (n) {
var i = Math.random(),
q = Math.random();
if (n == 2) {
i = (i < 0.5) ? -(1/2) : 1/2;
q = (q < 0.5) ? -(1/2) : 1/2;
} else if (n == 4) {
if (0 <= i && i < 0.25) i = -(3/2);
else if (0.25 <= i && i < 0.5) i = -(1/2);
else if (0.5 <= i && i < 0.75) i = 1/2;
else i = 3/2;
if (0 <= q && q < 0.25) q = -(3/2);
else if (0.25 <= q && q < 0.5) q = -(1/2);
else if (0.5 <= q && q < 0.75) q = 1/2;
else q = 3/2;
} else if (n == 8) {
if (0 <= i && i < 0.125) i = -(7/2);
else if (0.125 <= i && i < 0.25) i = -(5/2);
else if (0.25 <= i && i < 0.375) i = -(3/2);
else if (0.375 <= i && i < 0.5) i = -(1/2);
else if (0.5 <= i && i < 0.625) i = 1/2;
else if (0.625 <= i && i < 0.75) i = 3/2;
else if (0.75 <= i && i < 0.875) i = 5/2;
else i = 7/2;
if (0 <= q && q < 0.125) q = -(7/2);
else if (0.125 <= q && q < 0.25) q = -(5/2);
else if (0.25 <= q && q < 0.375) q = -(3/2);
else if (0.375 <= q && q < 0.5) q = -(1/2);
else if (0.5 <= q && q < 0.625) q = 1/2;
else if (0.625 <= q && q < 0.75) q = 3/2;
else if (0.75 <= q && q < 0.875) q = 5/2;
else q = 7/2;
} else {
i = 0; q = 0;
}
return [i, q];
}
function qam(i, q, f, t) {
return (i*Math.cos(Math.PI*2*f*t) - q*Math.sin(2*Math.PI*f*t));
}
function fft(s) {
var a = 0;
var e = [], E = []; // even
var o = [], O = []; // odd
var N = s.length;
var r = new Array(N); // result
if (N > 1) {
for (var n = 0; n < N; n++) {
if (n%2 === 0) {
e.push(s[n]);
} else {
o.push(s[n]);
}
}
E = fft(e); O = fft(o);
for (var k = 0; k < N/2; k++) {
a = 2*Math.PI*k/N;
// (E[k][0] + jE[k][1]) + (cos(a) - jsin(a))*(O[k][0] + jO[k][1])
r[k] = [
E[k][0] + (Math.cos(a)*O[k][0] + Math.sin(a)*O[k][1]),
E[k][1] + (Math.cos(a)*O[k][1] - Math.sin(a)*O[k][0])];
r[k + N/2] = [
E[k][0] - (Math.cos(a)*O[k][0] + Math.sin(a)*O[k][1]),
E[k][1] - (Math.cos(a)*O[k][1] - Math.sin(a)*O[k][0])];
}
} else {
r[0] = [s[0], 0];
}
return r;
}
function loop() {
var levels = 8, // number of levels
iq; // In-phase and Quadrature components
var symbols = updateInterval/Ts,
samplesPerSymbol = Ts*sr;
for (var j = 0; j < symbols; j++) {
iq = getSymbol(levels);
// sample that symbol
for (var n = 0; n < samplesPerSymbol; n++) {
if (buffer.length >= N) {
buffer.splice(0, 1);
}
buffer.push(qam(iq[0], iq[1], freq, n/sr));
}
}
F = fft(buffer);
// Spectrum
for (var j = 0; j < F.length; j++) {
Sy[j] = 10*Math.log(Math.sqrt(Math.pow(F[j][0], 2) + Math.pow(F[j][1], 2)));
}
// Plot the time serie
//graph.clear(options).plot(buffer, x, [256, 511], [-4, 4]);
// Plot the spectrum
graph.clear(options).plot(Sy, Sx, [0,0.5], [0,70]);
// Constellation
//graph.clear(options).points([iq[0]], [iq[1]], [-4, 4], [-4, 4]);
setTimeout(loop, 1000*updateInterval);
}
loop();