Published
Edited
Feb 22, 2022
1 star
Insert cell
Insert cell
Insert cell
C_above_middle_C_freq = 440 * Math.pow(2, 3 / 12)
Insert cell
Insert cell
# 16-bit implementation
Insert cell
Insert cell
FreqBaseNote = 2616 // frequency of middle C
Insert cell
Insert cell
FreqMultipliers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map((o) =>
Math.pow(2, o / 12)
)
Insert cell
Insert cell
Insert cell
FreqMultiplyTable = FreqMultipliers.map((fm) => Math.round(fm * Math.pow(2, 8)))
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
midiNoteToFreqIntSpc = (note) => {
const memOctFreq = 0x10;
const memFreqTable = 0x20;
const memNoteMultiply = 0x00;
const memResult = 0x02;

const baseOct = 5;

var s = SPC.createState();

// Write initial values to memory addresses
s = SPC.write16(s, memOctFreq, FreqBaseNote);

// Load multiplication table
s = FreqMultiplyTable.reduce(
(s, fm, i) => SPC.write16(s, memFreqTable + i * 2, fm),
s
);

// Divide note by 12 to figure out octave number, and note index within octave.
s = SPC.ldy(s, 0);
s = SPC.lda(s, note);
s = SPC.ldx(s, 12);
// This instruction results in A = YA / X, Y = YA % X
s = SPC.div(s);

/* Y has the note index
A has the octave number
We'll subtract with the base octave so we know how much to double or halve.
*/

s = SPC.sec(s);
s = SPC.sbc(s, baseOct);

// loop where we double / halve the octave

if (SPC.bne(s)) {
if (SPC.bmi(s)) {
do {
s = SPC.clc(s);
// divide in half (16-bit LSR)
s = SPC.rorAddr(s, addrOctFreq + 1);
s = SPC.rorAddr(s, addrOctFreq);
s = SPC.inc(s);
} while (SPC.bne(s));
} else {
do {
s = SPC.clc(s);
// multiply by two (16-bit ASL)
s = SPC.rolAddr(s, addrOctFreq);
s = SPC.rolAddr(s, addrOctFreq + 1);
s = SPC.dec(s);
} while (SPC.bne(s));
}
}

/* Take the note index in Y and double it into X
to use as an index into the freq table. Store the multiplier in memory.
*/
s = SPC.tya(s);
s = SPC.asl(s);
s = SPC.tax(s);

s = SPC.ldaAddrX(s, memFreqTable + 1);
s = SPC.staAddr(s, memNoteMultiply + 1);
s = SPC.ldaAddrX(s, memFreqTable);

s = SPC.staAddr(s, memNoteMultiply);

/* Begin the long process of applying the note multiplier to the octave frequency.
This is where the complex integer based floating point multiplication starts.
*/

s = SPC.ldyAddr(s, memOctFreq);
// This instruction results in YA = Y * A
s = SPC.mul(s);
s = SPC.tya(s);
s = SPC.ldy(s, 0);
s = SPC.styaAddr(s, memResult);

s = SPC.ldyAddr(s, memOctFreq);
s = SPC.ldaAddr(s, memNoteMultiply + 1);
s = SPC.mul(s);
s = SPC.addw(s, memResult);
s = SPC.styaAddr(s, memResult);

s = SPC.ldyAddr(s, memOctFreq + 1);
s = SPC.ldaAddr(s, memNoteMultiply);
s = SPC.mul(s);
s = SPC.addw(s, memResult);
s = SPC.styaAddr(s, memResult);

s = SPC.ldyAddr(s, memOctFreq + 1);
s = SPC.ldaAddr(s, memNoteMultiply + 1);
s = SPC.mul(s);
s = SPC.tay(s);
s = SPC.lda(s, 0);
s = SPC.addw(s, memResult);
s = SPC.styaAddr(s, memResult);

// By now we have our calculated frequency value!
return SPC.read16(s, memResult);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
testFrequencies = notes.map(midiNoteToFreqIntSpc)
Insert cell
Insert cell
floatReference = notes.map((n) => Math.round(midiNoteToFreq(n)))
Insert cell
Insert cell
variation = testFrequencies.map((f, i) =>
pct(roundTo(3, (f - floatReference[i]) / floatReference[i]))
)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
s0 = SPC.createState()
Insert cell
Insert cell
s0a = SPC.write16(s0, addrOctFreq, simBaseFreq)
Insert cell
Insert cell
Insert cell
s1 = FreqMultiplyTable.reduce(
(s, fm, i) => SPC.write16(s, addrFreqTable + i * 2, fm),
s0a
)
Insert cell
Insert cell
Insert cell
s2 = SPC.ldy(s1, 0)
Insert cell
s3 = SPC.lda(s2, simInputNote)
Insert cell
s4 = SPC.ldx(s3, 12)
Insert cell
Insert cell
s5 = SPC.div(s4)
Insert cell
Insert cell
s6 = SPC.sec(s5)
Insert cell
s7 = SPC.sbc(s6, simBaseOct)
Insert cell
Insert cell
Insert cell
s7a = {
var s = s7;

if (SPC.bne(s)) {
if (SPC.bmi(s)) {
return SPC.bloop(
s,
(s) => {
s = SPC.clc(s);
// divide in half (16-bit LSR)
s = SPC.rorAddr(s, addrOctFreq + 1);
s = SPC.rorAddr(s, addrOctFreq);
return SPC.inc(s);
},
SPC.bne
);
} else {
return SPC.bloop(
s,
(s) => {
s = SPC.clc(s);
// multiply by two (16-bit ASL)
s = SPC.rolAddr(s, addrOctFreq);
s = SPC.rolAddr(s, addrOctFreq + 1);
return SPC.dec(s);
},
SPC.bne
);
}
}

return s;
}
Insert cell
Insert cell
Insert cell
s8 = SPC.tya(s7a)
Insert cell
s9 = SPC.asl(s8)
Insert cell
s10 = SPC.tax(s9)
Insert cell
s11 = SPC.ldaAddrX(s10, addrFreqTable + 1)
Insert cell
s12 = SPC.staAddr(s11, addrNoteMultiply + 1)
Insert cell
s13 = SPC.ldaAddrX(s12, addrFreqTable)
Insert cell
s14 = SPC.staAddr(s13, addrNoteMultiply)
Insert cell
Insert cell
Insert cell
s15 = SPC.ldyAddr(s14, addrOctFreq)
Insert cell
Insert cell
s16 = SPC.mul(s15)
Insert cell
s16a = SPC.tya(s16)
Insert cell
s16b = SPC.ldy(s16a, 0)
Insert cell
s17 = SPC.styaAddr(s16b, addrResult)
Insert cell
Insert cell
s18 = SPC.ldyAddr(s17, addrOctFreq)
Insert cell
s19 = SPC.ldaAddr(s18, addrNoteMultiply + 1)
Insert cell
s20 = SPC.mul(s19)
Insert cell
s21 = SPC.addw(s20, addrResult)
Insert cell
s22 = SPC.styaAddr(s21, addrResult)
Insert cell
Insert cell
s23 = SPC.ldyAddr(s22, addrOctFreq + 1)
Insert cell
s24 = SPC.ldaAddr(s23, addrNoteMultiply)
Insert cell
s26 = SPC.mul(s24)
Insert cell
s27 = SPC.addw(s26, addrResult)
Insert cell
s28 = SPC.styaAddr(s27, addrResult)
Insert cell
Insert cell
s29 = SPC.ldyAddr(s28, addrOctFreq + 1)
Insert cell
s30 = SPC.ldaAddr(s29, addrNoteMultiply + 1)
Insert cell
s31 = SPC.mul(s30)
Insert cell
s31a = SPC.tay(s31)
Insert cell
s31b = SPC.lda(s31a, 0)
Insert cell
s32 = SPC.addw(s31b, addrResult)
Insert cell
s33 = SPC.styaAddr(s32, addrResult)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
MIDDLE_A_NOTE = 69
Insert cell
MIDDLE_A_FREQ = 4400
Insert cell
midiNoteToFreq = (n) => Math.pow(2, (n - 69) / 12) * MIDDLE_A_FREQ
Insert cell
Insert cell
shift = 8
Insert cell
BaseFreq = 5233
Insert cell
NoteBase = 60
Insert cell
note = 69
Insert cell
offset = note - NoteBase
Insert cell
octave = Math.floor(offset / 12)
Insert cell
octaveBase = {
var f = BaseFreq;
if (octave == 0) return f;
for (
var i = 0;
(octave > 0 && i < octave) || (octave < 0 && i > octave);
i += octave > 0 ? 1 : -1
) {
if (octave > 0) {
f = f << 1;
continue;
}
f = f >> 1;
}
return f;
}
Insert cell
noteMultiplier = FreqMultiplyTable[offset]
Insert cell
loNoteMultiplier = (noteMultiplier & 0xff) << 8
Insert cell
hiNoteMultiplier = noteMultiplier & 0xff00
Insert cell
loFreqToMultiply = (BaseFreq >> 8) << shift
Insert cell
hiFreqToMultiply = (BaseFreq & 0xff) << shift
Insert cell
multiplied = multx(BaseFreq, noteMultiplier)
Insert cell
result = multiplied
Insert cell
mult = (x, y) =>
Math.round((x << shift) * Math.round(y * Math.pow(2, shift))) >> (shift * 2)
Insert cell
mult(200, 1.5)
Insert cell
9*9
Insert cell
9*90
Insert cell
Insert cell
multx = (x, y) => {
const p1 = ((x & 0xff) * (y & 0xff)) & 0xffff;
const p2 = (((x & 0xff) * (y >> 8)) & 0xffff) << 8;

const p3 = ((x >> 8) * (y & 0xff)) & 0xffff;
const p4 = (((x >> 8) * (y >> 8)) & 0xffff) << 8;

return (((p1 + p2) & 0xffff) >> 8) + ((p3 + p4) & 0xffff);
}
Insert cell
multx(512, 384)
Insert cell
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more