Midi

This sound module generates a waveform by dividing a clock signal by the desired frequency. Sampled waveforms are stored in an EEPROM and then outputted through a DAC (r2r ladder). The EEPROM is programmable to allow adding new waveforms.

Waveforms

Square
Triangle
Sawtooth

Samples

Frozen – Let it Go
Zelda NES
Paint it Black

Frequencies / Formula

RangeInitial FrequencyDivided Range
0x028912Hz738Hz – 147Hz
0x157824Hz1476Hz – 293Hz
0x2115648Hz2953Hz – 587Hz
0x31231296Hz5906Hz – 1175Hz

Precision

Generally, the higher the number you choose to divide by, the more precision there will be. You will notice overlaps in the range of frequencies above.

ie. 523Hz (C5) can be reached on ranges 0x0 and 0x1. However, the one that would be most accurate is most likely 0x1.

0x1) 57824Hz / 523Hz = 0b01101110 ✓

0x0) 28912Hz / 523Hz = 0b00110111

EEPROM and Waveforms

I have decided to go with loading calculated waveforms into an EEPROM. Each sample takes up an equal amount of space, 0xFF, and uses the addresses [A0..A7]

I then use the next two bits of address space to select the range of frequencies (see above). [A8..A9]

The rest of the address lines are used for choosing which waveform I want to use. ie. Square, Triangle, Sawtooth, Noise, Sine, etc…

The output of the EEPROM, [D0..D7] feeds directly into an R2R ladder, to convert the digital signal into an analog one.

RegisterBit(s)Value
Metaxxxxxx11Range Select
Metaxxxx11xxWaveform Select
Meta1111xxxxVolume Select
Division11111111Range Freq Divisor

Waveforms in EEPROM

I used Javascript (NodeJs) to help me generate this data since its the language I am most comfortable with.

As you might notice, for the square wave the max value I am using is 0xEF and similarly, for the triangle, I am going from 0x80 to 0xFF. There may be a better way, but this way the output volume is almost equal across all the waveforms.

Here are some examples:

// SQUARE (1 cycle)
for (let e = 0; e <= 255; e = e + 1) {
    eepromData[address] = e < 128 ? 0xef : 0x0;
    address++;
}
// SAWTOOTH (1 cycle) - Ramp Up
for (let e = 0; e <= 255; e = e + 1) {
    eepromData[address] = e;
    address++;
}
// TRIANGLE (1 cycle)
val = 0;
for (let e = 0; e <= 254; e = e + 1) {
    eepromData[address] = val;
    if (e < 128) {
        val = val + 2;
        if (val > 255) val = 255;
    } else {
        val = val - 2;
    }
    address++;
}
Square Waveform, 1-cycle
Square Waveform, 8-cycles (8x)
Sawtooth (Ramp-up) Waveform 1-cycle
Sawtooth (Ramp-up) Waveform 8-cycles
Triangle Waveform 1-cycle
Triangle Waveform 8-cycles

Generating Music!

My goal was to be able to convert a standard MIDI file and play it back, plain and simple. To do this, first I created a small Node script that loaded the file and ran through…. TODO TODO TODO