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 }; ----