mirror of
https://github.com/mrkite/soundsmith.git
synced 2024-06-13 05:29:27 +00:00
483 lines
16 KiB
Plaintext
483 lines
16 KiB
Plaintext
DOC info and Player Pseudocode
|
|
==============================
|
|
|
|
== DOC
|
|
|
|
The DOC in the IIgs is a multi-channel digital oscillator. There are
|
|
32 oscillators, operating in pairs. There is 64k of sound RAM, which holds
|
|
the wavetable. The oscillators address into soundram and determine what
|
|
sound data to send to the speaker.. the oscillators operate at a set
|
|
frequency.
|
|
|
|
There are four registers for controlling the DOC.
|
|
|
|
`$c03c`::
|
|
Sound Control. This uses various bits to control the other register modes.
|
|
Bit 7::: DOC busy flag. 1 - DOC is busy
|
|
Bit 6::: DOC or SoundRAM access. 0 - DOC
|
|
Bit 5::: Address auto-increment. 1 - enabled
|
|
Bit 4::: reserved
|
|
Bits 3--0::: Master volume, 0 - low, 15 - high
|
|
`$c03d`::
|
|
Sound Data. This is used to read and write to and from the DOC and
|
|
SoundRAM. If auto-increment is enabled, reading or writing to this register
|
|
will auto-increment the address register. Note, when reading, the register
|
|
lags by one cycle. You'll need to throw away the first read after modifying
|
|
the address registers.
|
|
`$c03e`::
|
|
Address Low. This is the address into either the DOC or the SoundRAM.
|
|
`$c03f`::
|
|
Address High. This is the address into SoundRAM. When accessing the DOC,
|
|
only the low byte of the address register is used.
|
|
|
|
=== DOC Addresses
|
|
|
|
When in DOC mode, you can modify various settings by setting the low address
|
|
register to various addresses and writing and reading from the data register.
|
|
The following are the various addresses used.
|
|
|
|
==== Oscillator Interrupt $E0
|
|
|
|
Contains which oscillator triggered an interrupt.
|
|
|
|
Bit 7::
|
|
Interrupt occurred, 1 - yes
|
|
Bits 5--1::
|
|
Oscillator number that triggered the interrupt
|
|
|
|
==== Oscillator Enable $E1
|
|
|
|
The number of oscillators running. Multiply the number of desired oscillators
|
|
by two, and set. Any number from 2 to 64 is valid. 2 is the default
|
|
(1 oscillator).
|
|
|
|
==== A/D Converter $E2
|
|
|
|
This is the current value of the analog input.
|
|
|
|
==== Wavetable Size $C0--$DF
|
|
|
|
Control the size of the wavetable for each oscillator. $C0 controls
|
|
oscillator 0, $DF controls oscillator 31.
|
|
|
|
Bits 5--3::
|
|
Table size.
|
|
0::: 256
|
|
1::: 512
|
|
2::: 1024
|
|
3::: 2048
|
|
4::: 4096
|
|
5::: 8192
|
|
6::: 16384
|
|
7::: 32768
|
|
Bits 2--0::
|
|
Address resolution. See below for the wavetable address calculation.
|
|
|
|
==== Oscillator Control $A0--$BF
|
|
|
|
Control the oscillator behavior. $A0 is for oscillator 0, $BF is for
|
|
oscillator 31.
|
|
|
|
Bits 7--4::
|
|
Which hardware channel to use.
|
|
Bit 3::
|
|
Interrupt enable, 1 - interrupts enabled
|
|
Bits 2--1::
|
|
Oscillator mode
|
|
0::: Free Run. Starts at beginning of wavetable and repeats same
|
|
wavetable. Halts when halt bit is set, or 0 occurs in wavetable.
|
|
1::: One Shot. Start at beginning of wavetable, step through once,
|
|
stop at end of table.
|
|
2::: Sync. When even-numbered oscillator starts, the oscillator above
|
|
it will synchronize and begin simulatenously.
|
|
3::: Swap. When even-numbered oscillator reaches end of wavetable,
|
|
it resetsthe accumulator to 0, sets the halt bit, and clears the
|
|
halt bit of the oscillator above it.
|
|
Bit 0::
|
|
Halt bit. 1 - Oscillator is halted.
|
|
|
|
==== Wavetable Pointers $80--$9F
|
|
|
|
The start page of each oscillator's wavetable. Each page is 256 bytes long.
|
|
$80 is the start page of oscillator 0, $9F is the start page of oscillator 31.
|
|
|
|
==== Oscillator Data $60--$7F
|
|
|
|
The last byte read fro the wavetable for each oscillator. $60 is oscillator
|
|
0, $7F is oscillator 31.
|
|
|
|
==== Volume $40--$5F
|
|
|
|
The oscillator's volume. The current wavetable data byte is multiplied
|
|
by the 8-bit volume to obtain the final output level. $40 is the
|
|
volume for oscillator 0, $5F is for oscillator 31.
|
|
|
|
==== Frequency High and Low $00--$3F
|
|
|
|
This is a 16-bit value for each oscillator. $00 is the low byte of the
|
|
frequency for oscillator 0, $20 is the high byte for oscillator 0.
|
|
|
|
This determines the speed the wavetable is read from memory.
|
|
|
|
Output Frequency = F * SR / (2 ^ (17 + RES))
|
|
|
|
SR = 894.886KHz / (OSC + 2).
|
|
|
|
RES = Wavetable resolution
|
|
|
|
F = 16-bit frequency
|
|
|
|
OSC = number of enabled oscillators
|
|
|
|
=== Wavetable Address Calculation
|
|
|
|
Each oscillator has a 24-bit accumulator. Each time the oscillator
|
|
updates, the 16-bit value from the oscillator's Frequency is added to
|
|
the accumulator. The result is then passed to a multiplexer to determine
|
|
the final 16-bit SoundRAM address. The Table Size, Wavetable Pointer, and
|
|
Resolution all determine how the multiplexer works. Use the following
|
|
table to determine how to calcualte the final address. The Pointer
|
|
register determines the high bits of the address, the accumulatr determines
|
|
the low bits.
|
|
|
|
[width="50%",options="header"]
|
|
|==========================
|
|
|Table Size|Resolution|Pointer Reg|Accumulator
|
|
|256|7|P7--P0|A23--A16
|
|
|256|6|P7--P0|A22--A15
|
|
|256|5|P7--P0|A21--A14
|
|
|256|...|...|...
|
|
|256|0|P7--P0|A16--A9
|
|
|512|7|P7--P1|A23--A15
|
|
|512|6|P7--P1|A22--A14
|
|
|512|...|...|...
|
|
|512|0|P7--P1|A16--A8
|
|
|1024|7|P7--P2|A23--A14
|
|
|1024|6|P7--P2|A22--A13
|
|
|1024|...|...|...
|
|
|1024|0|P7--P2|A16--A7
|
|
|2048|7|P7--P3|A23--A13
|
|
|2048|6|P7--P3|A22--A12
|
|
|2048|...|...|...
|
|
|2048|0|P7--P3|A16--A6
|
|
|4096|7|P7--P4|A23--A12
|
|
|4096|6|P7--P4|A22--A11
|
|
|4096|...|...|...
|
|
|4096|0|P7--P4|A16--A5
|
|
|8192|7|P7--P5|A23--A11
|
|
|8192|6|P7--P5|A22--A10
|
|
|8192|...|...|...
|
|
|8192|0|P7--P5|A16--A4
|
|
|16384|7|P7--P6|A23--A10
|
|
|16384|6|P7--P6|A22--A9
|
|
|16384|...|...|...
|
|
|16384|0|P7--P6|A16--A3
|
|
|32768|7|P7|A23--A9
|
|
|32768|6|P7|A22--A8
|
|
|32768|...|...|...
|
|
|32768|0|P7|A16--A2
|
|
|==========================
|
|
|
|
The 32 oscillators are serviced in sequence. With all oscillators
|
|
enabled, the DOC takes 38 microseconds to service all 32. 1.2 microseconds
|
|
per oscillator.
|
|
|
|
== Player
|
|
|
|
This is pseudocode for the soundsmith music player. The pseudocode style
|
|
is basically C-style, with 68k-style word notation.
|
|
|
|
For example: `music[8].w` means read a little-endian word from the music
|
|
array, starting at byte offset 8.
|
|
|
|
[source,c]
|
|
----
|
|
// parse the song headers and prep audio
|
|
void initSong() {
|
|
SNDCTL = CURVOL & 0xf; // set vol, enable DAC, disable autoinc
|
|
// reset all oscillators to halt + freerun
|
|
for (int osc = 0xa0; osc < 0xc0; osc++) {
|
|
SNDADRL = osc;
|
|
SNDDAT = 1; // halt
|
|
}
|
|
|
|
// load wavebank into sound RAM
|
|
SNDCTL = 0x60; // enable RAM + autoinc
|
|
SNDADRL = 0;
|
|
SNDADRH = 0; // point to beginning of sound RAM
|
|
// do all 64k of sound RAM
|
|
for (int addr = 0; addr < 0x10000; addr++) {
|
|
SNDDAT = wavebank[addr + 2]; // skip num inst at start of wavebank
|
|
}
|
|
playing = false;
|
|
|
|
SNDINT = 0x945c; // = jsr $00/945c, this is soundInt()
|
|
SNDINTH = 0x003c; // called whenever a channel stops
|
|
|
|
SNDCTL = 0; // DAC, vol = 0, disable autoinc
|
|
|
|
// use oscillator 0 as a timer
|
|
SNDADRL = 0x0; // Osc 0 Frequency
|
|
SNDDAT = 0xfa;
|
|
SNDADRL = 0x20; // Osc 0 Frequency Hi
|
|
SNDDAT = 0;
|
|
SNDADRL = 0x40; // Osc 0 Volume
|
|
SNDDAT = 0; // mute the timer
|
|
SNDADRL = 0x80; // Osc 0 Wavetable ptr
|
|
SNDDAT = 0;
|
|
SNDADRL = 0xc0; // Osc 0 Wavetable size
|
|
SNDDAT = 0; // 0 = 256 bytes, 0 res
|
|
|
|
SNDADRL = 0xe1; // Enable oscillators
|
|
SNDDAT = 0x3c; // 30 oscillators
|
|
SNDADRL = 0xa0; // Osc 0 control
|
|
SNDDAT = 0x8; // free run mode + interrupts enabled
|
|
|
|
// music + header + blockSize = effects1 table (blockSize bytes long)
|
|
effects1 = &music + 0x258 + music[6].w;
|
|
// effects1 + blockSize = effects2 table (blockSize bytes long)
|
|
effects2 = effects1 + music[6].w;
|
|
// effects2 + blockSize = stereo table (16 words long)
|
|
stereoTable = effects2 + music[6].w;
|
|
|
|
// load instrument headers
|
|
int pos = 0;
|
|
numInst = wavebank[0] & 0xff;
|
|
for (inst = 0; inst < numInst; inst++) {
|
|
for (int i = 0; i < 12; i++) {
|
|
instdef[inst * 12 + i] = wavebank[0x10022 + pos++];
|
|
}
|
|
pos += 0x50;
|
|
}
|
|
// load compact table
|
|
for (int y = 0; y < 0x20; y++) {
|
|
compactTable[y] = wavebank[0x1005e + pos++];
|
|
}
|
|
}
|
|
|
|
// start playing the song
|
|
void playSong() {
|
|
timer = 0;
|
|
songLen = music[0x1d6];
|
|
curRow = 0;
|
|
curPattern = 0;
|
|
rowOffset = music[0x1d8] * 64 * 14;
|
|
tempo = music[8].w;
|
|
|
|
int pos = 0;
|
|
for (int i = 0; i < 0x1e; i += 2) {
|
|
volumeTable[i].w = music[0x2c + pos].w;
|
|
pos += 0x1e;
|
|
}
|
|
playing = true;
|
|
}
|
|
|
|
// this is called whenever an oscillator halts with interrupts enabled
|
|
void soundInt() {
|
|
SNDCTL &= 0x9f; // doc, no auto inc
|
|
SNDADRL = 0xe0; // oscillator interrupt
|
|
SNDDAT &= 0x7f; // clear interrupt
|
|
uint8_t osc = (SNDDAT & 0x3e) >> 1; // get fired oscillator
|
|
if (osc != 0) { // wasn't timer
|
|
SNDADRL = 0xa0 + osc; // osc control
|
|
if (SNDDAT & 8) { // were interrupts enabled?
|
|
SNDDAT &= 0xfe; // clear halt bit.. retrig
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!playing)
|
|
return;
|
|
timer++;
|
|
if (timer == tempo) {
|
|
timer = 0;
|
|
for (oscillator = 0; oscillator < 0xe; oscillator++) {
|
|
semitone = music[0x258 + rowOffset];
|
|
if (semitone == 0 || semitone >= 0x80) {
|
|
rowOffset++;
|
|
if (semitone == 0x80) {
|
|
SNDCTL &= 0x9f; // DAC mode
|
|
oscaddr = (oscillator + 1) * 2;
|
|
SNDADRL = 0xa0 + oscaddr; // osc control
|
|
SNDDAT = 1; // halt
|
|
SNDADRL = 0xa0 + oscaddr + 1; // osc control
|
|
SNDDAT = 1; // halt pair
|
|
} else if (semitone == 0x81) {
|
|
curRow = 0x3f;
|
|
}
|
|
} else {
|
|
uint8_t fx = effects1[rowOffset];
|
|
uint8_t inst = fx & 0xf0;
|
|
if (!inst)
|
|
inst = prevInst[oscillator];
|
|
prevInst[oscillator] = inst;
|
|
volumeInt = volumeTable[((inst >> 4) - 1) * 2].w / 2;
|
|
fx &= 0xf;
|
|
if (fx == 0) {
|
|
arpeggio[oscillator] = effects2[rowOffset];
|
|
arpTone[oscillator] = semitone;
|
|
} else {
|
|
arpeggio[oscillator] = 0;
|
|
if (fx == 3) {
|
|
volumeInt = effects2[rowOffset] / 2;
|
|
} else if (fx == 6) {
|
|
volumeInt -= effects2[rowOffset] / 2;
|
|
if (volumeInt < 0)
|
|
volumeInt = 0;
|
|
} else if (fx == 5) {
|
|
volumeInt += effects2[rowOffset] / 2;
|
|
if (volumeInt >= 0x80)
|
|
volumeInt = 0x7f;
|
|
} else if (fx == 0xf) {
|
|
tempo = effects2[rowOffset];
|
|
}
|
|
if ((fx == 3 || fx == 5 || fx == 6) && semitone == 0) {
|
|
while (SNDCTL & 0x80); // wait for DOC
|
|
SNDCTL = (SNDCTL | 0x20) & 0xbf; // DOC + autoinc
|
|
SNDADRL = 0x40 + (oscillator + 1) * 2; // osc volume
|
|
SNDDAT = volumeInt;
|
|
SNDDAT = volumeInt; // pair
|
|
}
|
|
}
|
|
|
|
if (semitone) {
|
|
oscaddr = (oscillator + 1) * 2;
|
|
SNDCTL &= 0x9f; // DOC mode
|
|
SNDADRL = 0xa0 + oscaddr; // osc ctl
|
|
SNDDAT = (SNDDAT & 0xf7) | 1; // halt, no interrupt
|
|
SNDADRL = 0xa0 + oscaddr + 1; // osc ctl pair
|
|
SNDDAT = (SNDDAT & 0xf7) | 1; // halt, no interrupt pair
|
|
inst = (prevInst[oscillator] >> 4) - 1;
|
|
if (inst < numInst) {
|
|
int x = inst * 12;
|
|
while (instruments[x].b < semitone) {
|
|
x += 6;
|
|
}
|
|
oscAptr = instruments[x + 1].b;
|
|
oscAsiz = instruments[x + 2].b;
|
|
oscActl = instruments[x + 3].b;
|
|
if (stereo) {
|
|
oscActl &= 0xf;
|
|
if (stereoTable[oscillator * 2])
|
|
oscActl |= 0x10;
|
|
}
|
|
while (instruments[x].b != 0x7f) {
|
|
x += 6;
|
|
}
|
|
x += 6; // skip last instdef
|
|
while (instruments[x] < semitone) {
|
|
x += 6;
|
|
}
|
|
oscBptr = instruments[x + 1].b;
|
|
oscBsiz = instruments[x + 2].b;
|
|
oscBctl = instruments[x + 3].b;
|
|
if (stereo) {
|
|
oscBctl &= 0xf;
|
|
if (stereoTable[oscillator * 2])
|
|
oscBctl |= 0x10;
|
|
}
|
|
freq = freqTable[semitone * 2].w >> compactTable[inst * 2].w;
|
|
while (SNDCTL & 0x80); // wait for DOC
|
|
SNDCTL = (SNDCTL | 0x20) & 0xbf; // DOC + autoinc
|
|
SNDADRL = oscaddr; // osc freq lo
|
|
SNDDAT = freq;
|
|
SNDDAT = freq; // pair
|
|
SNDADRL = 0x20 + oscaddr; // osc freq hi
|
|
SNDDAT = freq >> 8;
|
|
SNDDAT = freq >> 8; // pair
|
|
SNDADRL = 0x40 + oscaddr; // osc volume
|
|
SNDDAT = volumeConversion[volumeInt];
|
|
SNDDAT = volumeConversion[volumeInt]; // pair
|
|
SNDADRL = 0x80 + oscaddr; // osc wavetable ptr
|
|
SNDDAT = oscAptr;
|
|
SNDDAT = oscBptr; // pair
|
|
SNDADRL = 0xc0 + oscaddr; // osc wavetable size
|
|
SNDDAT = oscAsiz;
|
|
SNDDAT = oscBsiz; // pair
|
|
SNDADRL = 0xa0 + oscaddr; // osc ctl
|
|
SNDDAT = oscActl;
|
|
SNDDAT = oscBctl; // pair
|
|
}
|
|
}
|
|
rowOffset++;
|
|
}
|
|
}
|
|
curRow++;
|
|
if (curRow < 0x40)
|
|
return;
|
|
// advance pattern
|
|
curRow = 0;
|
|
curPattern++;
|
|
if (curPattern < songLen) {
|
|
rowOffset = music[0x1d8 + curPattern] * 64 * 14;
|
|
} else { // stopped
|
|
playing = false;
|
|
}
|
|
return;
|
|
} else { // between notes.. apply arpeggios
|
|
for (oscillator = 0; oscillator < 0xe; oscillator++) {
|
|
if (arpeggio[oscillator]) {
|
|
switch (timer % 6) {
|
|
case 1: case 4:
|
|
arpTone[oscillator] += arpeggio[oscillator] >> 4;
|
|
break;
|
|
case 2: case 5:
|
|
arpTone[oscillator] += arpeggio[oscillator] & 0xf;
|
|
break;
|
|
case 0: case 3:
|
|
arpTone[oscillator] -= arpeggio[oscillator] >> 4;
|
|
arpTone[oscillator] -= arpeggio[oscillator] & 0xf;
|
|
break;
|
|
}
|
|
freq = freqTable[arpTone[oscillator] * 2].w >> compactTable[oscillator *
|
|
2].w;
|
|
oscaddr = (oscillator + 1) * 2;
|
|
while (SNDCTL & 0x80); // wait for DOC
|
|
SNDCTL = (SNDCTL | 0x20) & 0xbf; // DOC + autoinc
|
|
SNDADRL = oscaddr; // freq lo
|
|
SNDDAT = freq;
|
|
SNDDAT = freq; // pair
|
|
SNDADRL = 0x20 + oscaddr; // freq hi
|
|
SNDDAT = freq >> 8;
|
|
SNDDAT = freq >> 8; // pair
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uint8_t volumeConversion[] = {
|
|
0x00, 0x02, 0x04, 0x05, 0x06, 0x07, 0x09, 0x0a, // 0
|
|
0x0c, 0x0d, 0x0f, 0x10, 0x12, 0x13, 0x15, 0x16, // 8
|
|
0x18, 0x19, 0x1b, 0x1c, 0x1e, 0x1f, 0x21, 0x22, // 10
|
|
0x24, 0x25, 0x27, 0x28, 0x2a, 0x2b, 0x2d, 0x2e, // 18
|
|
0x30, 0x31, 0x33, 0x34, 0x36, 0x37, 0x39, 0x3a, // 20
|
|
0x3c, 0x3d, 0x3f, 0x40, 0x42, 0x43, 0x45, 0x46, // 28
|
|
0x48, 0x49, 0x4b, 0x4c, 0x4e, 0x4f, 0x51, 0x52, // 30
|
|
0x54, 0x55, 0x57, 0x58, 0x5a, 0x5b, 0x5d, 0x5e, // 38
|
|
0x60, 0x61, 0x63, 0x64, 0x66, 0x67, 0x69, 0x6a, // 40
|
|
0x6c, 0x6d, 0x6f, 0x70, 0x72, 0x73, 0x75, 0x76, // 48
|
|
0x78, 0x79, 0x7b, 0x7c, 0x7e, 0x7f, 0x81, 0x82, // 50
|
|
0x84, 0x85, 0x87, 0x88, 0x8a, 0x8b, 0x8d, 0x8e, // 58
|
|
0x90, 0x91, 0x93, 0x94, 0x96, 0x97, 0x99, 0x9a, // 60
|
|
0x9c, 0x9d, 0x9f, 0xa0, 0xa2, 0xa3, 0xa5, 0xa6, // 68
|
|
0xa8, 0xa9, 0xab, 0xac, 0xae, 0xaf, 0xb1, 0xb2, // 70
|
|
0xb4, 0xb5, 0xb7, 0xb8, 0xba, 0xbb, 0xbe, 0xc0 // 78
|
|
};
|
|
uint16_t freqTable[] = {
|
|
0x0000, 0x0016, 0x0017, 0x0018, 0x001a, 0x001b, 0x001d, 0x001e,
|
|
0x0020, 0x0022, 0x0024, 0x0026, 0x0029, 0x002b, 0x002e, 0x0031,
|
|
0x0033, 0x0036, 0x003a, 0x003d, 0x0041, 0x0045, 0x0049, 0x004d,
|
|
0x0052, 0x0056, 0x005c, 0x0061, 0x0067, 0x006d, 0x0073, 0x007a,
|
|
0x0081, 0x0089, 0x0091, 0x009a, 0x00a3, 0x00ad, 0x00b7, 0x00c2,
|
|
0x00ce, 0x00d9, 0x00e6, 0x00f4, 0x0102, 0x0112, 0x0122, 0x0133,
|
|
0x0146, 0x015a, 0x016f, 0x0184, 0x019b, 0x01b4, 0x01ce, 0x01e9,
|
|
0x0206, 0x0225, 0x0246, 0x0269, 0x028d, 0x02b4, 0x02dd, 0x0309,
|
|
0x0337, 0x0368, 0x039c, 0x03d3, 0x040d, 0x044a, 0x048c, 0x04d1,
|
|
0x051a, 0x0568, 0x05ba, 0x0611, 0x066e, 0x06d0, 0x0737, 0x07a5,
|
|
0x081a, 0x0895, 0x0918, 0x09a2, 0x0a35, 0x0ad0, 0x0b75, 0x0c23,
|
|
0x0cdc, 0x0d9f, 0x0e6f, 0x0f4b, 0x1033, 0x112a, 0x122f, 0x1344,
|
|
0x1469, 0x15a0, 0x16e9, 0x1846, 0x19b7, 0x1b3f, 0x1cde, 0x1e95,
|
|
0x2066, 0x2254, 0x245e, 0x2688
|
|
};
|
|
----
|