The Atari POKEY was the classic soundchip in the Atari 8-bit home computers and many 1980's arcade games. This clip shows some of my experiments in driving a POKEY from MIDI. A PIC receives MIDI data and two 74HC595 shift registers are used to assemble the 12 lines of bus data for the POKEY so it can be driven from a humble 14 pin PIC16F688. A 6N139 isolator is placed between MIDI in from PC and the PIC's serial input. The POKEY is clocked at 2MHz from the PIC's internal clock output.
I am using REAPER to sequence some MIDI files I found on the internet. Credit goes out to the authors of these MIDI files.. also to YouTube member little-scale, whose clips inspired me to poke about with the POKEY in the first place, and Bryan Edewaard, whose crib sheet I could not have done this without.
Here is the schematic for the circuit as built on breadboard (I am working on neater, stripboard based version)
And the source code for SOURCEBOOST C on the PIC16F688
#include <system.h>
#include <memory.h>
// PIC CONFIG (_INTRC_OSC_CLKOUT is needed so we output clock
// clock signal on pin 3)
#pragma DATA _CONFIG, _MCLRE_OFF & _WDT_OFF & _INTRC_OSC_CLKOUT
#pragma CLOCK_FREQ 8000000
typedef unsigned char byte;
// define the pins
#define P_DATA portc.0
#define P_SHCK portc.2
#define P_STCK portc.1
#define P_POKEY portc.3
// define "pure" tone sound mode. Other settings
// of bits 4-7 will add varying levels of distortion
#define POKEY_SOUNDMODE 0b10100000
// MIDI defs
#define MIDIMSG(b) ((b)>>4)
#define MIDICHAN(b) ((b)&0xf)
#define MIDIMSG_NOTEON 0x09
#define MIDIMSG_NOTEOFF 0x08
// structure for managing channel info
typedef struct
{
byte midiNote; // triggered MIDI note
byte note; // POKEY divider value
byte volume; // volume (bits 0-3)
byte count; // playing duration counter
} CHANNEL;
// Buffer to hold state of 4 POKEY voice channels
CHANNEL chan[4] = {0};
// MIDI message registers
byte runningStatus = 0;
byte midiParams[2] = {0};
byte numParams = 0;
byte thisParam = 0;
// Divider values for POKEY channels
byte notes[48] = {
250, // C#2
236, // D2
222, // D#2
210, // E2
198, // F2
187, // F#2
177, // G2
167, // G#2
157, // A2
148, // A#2
140, // B2
132, // C3
125, // C#3
118, // D3
111, // D#3
105, // E3
99, // F3
94, // F#3
88, // G3
83, // G#3
79, // A3
74, // A#3
70, // B3
66, // C4
62, // C#4
59, // D4
56, // D#4
52, // E4
50, // F4
47, // F#4
44, // G4
42, // G#4
39, // A4
37, // A#4
35, // B4
33, // C5
31, // C#5
29, // D5
28, // D#5
26, // E5
25, // F5
23, // F#5
22, // G5
21, // G#5
20, // A5
19, // A#5
18, // B5
17 // C6
};
////////////////////////////////////////////////////////////
// INITIALISE SERIAL PORT FOR MIDI
void init_usart()
{
pir1.1 = 1; //TXIF
pir1.5 = 0; //RCIF
pie1.1 = 0; //TXIE no interrupts
pie1.5 = 0; //RCIE no interrupts
baudctl.4 = 0; // SCKP synchronous bit polarity
baudctl.3 = 1; // BRG16 enable 16 bit brg
baudctl.1 = 0; // WUE wake up enable off
baudctl.0 = 0; // ABDEN auto baud detect
txsta.6 = 0; // TX9 8 bit transmission
txsta.5 = 1; // TXEN transmit enable
txsta.4 = 0; // SYNC async mode
txsta.3 = 0; // SEDNB break character
txsta.2 = 0; // BRGH high baudrate
txsta.0 = 0; // TX9D bit 9
rcsta.7 = 1; // SPEN serial port enable
rcsta.6 = 0; // RX9 8 bit operation
rcsta.5 = 1; // SREN enable receiver
rcsta.4 = 1; // CREN continuous receive enable
spbrgh = 0; // brg high byte
spbrg = 15; // brg low byte (31250)
}
////////////////////////////////////////////////////////////
// RECEIVE MIDI MESSAGE
// Return the status byte or 0 if nothing complete received
// caller must check midiParams array for byte 1 and 2
byte receiveMessage()
{
// loop until there is no more data or
// we receive a full message
for(;;)
{
// buffer overrun error?
if(rcsta.1)
{
rcsta.4 = 0;
rcsta.4 = 1;
}
// poll for a MIDI byte
if(!pir1.5)
{
// no data ready
return 0;
}
// read the character
byte q = rcreg;
pir1.5 = 0;
// is it a channel msg
if((q&0x80)>0)
{
numParams = 0;
thisParam = 0;
switch(q&0xf0)
{
case 0x80: // Note-off 2 key velocity
case 0x90: // Note-on 2 key veolcity
case 0xA0: // Aftertouch 2 key touch
case 0xB0: // Continuous controller 2 controller # controller value
case 0xC0: // Patch change 2 instrument #
case 0xE0: // Pitch bend 2 lsb (7 bits) msb (7 bits)
runningStatus = q;
numParams = 2;
break;
case 0xD0: // Channel Pressure 1 pressure
runningStatus = q;
numParams = 1;
break;
case 0xF0: // (non-musical commands) - ignore all data for now
runningStatus = 0;
return q;
}
}
// else do we have a channel message?
else if(runningStatus)
{
// fill in next command parameter
midiParams[thisParam++] = q;
if(thisParam>=numParams)
{
// return the command
thisParam = 0;
return runningStatus;
}
}
}
return 0;
}
////////////////////////////////////////////////////////////
// DRIVE DATA OUT TO SHIFT REGISTERS
// m is a bit mask to highest bit in the data
void dataOut(byte d, byte m)
{
while(m)
{
// shift clock low
P_SHCK = 0;
// data out
P_DATA = (d&m)?1:0;
// shift clock high
P_SHCK = 1;
// shift the mask
m>>=1;
}
}
////////////////////////////////////////////////////////////
// WRITE ADDRESS AND DATA TO POKEY
void writePokey(byte address, byte data)
{
// store clock low
P_STCK = 0;
// fill the shift regs
dataOut(address,0x08);
dataOut(data,0x80);
// store clock high
P_STCK = 1;
// pulse POKEY chip enable line
P_POKEY = 0;
delay_us(100);
P_POKEY = 1;
delay_us(100);
}
////////////////////////////////////////////////////////////
// POKEY RESET SEQUENCE
void resetPokey()
{
// fill all locations with 0
for(int i=0;i<16;++i)
writePokey(i, 0);
// reset sequence
writePokey(0x0f, 3);
writePokey(0x09, 1);
}
////////////////////////////////////////////////////////////
// HANDLE MIDI NOTE TRIGGER (ON OR OFF)
// MANAGES THE 4 VOICES
void handleNote(byte midiNote, byte midiVelocity)
{
int iAlreadyPlaying = -1;
int iFree = -1;
int iSteal = -1;
int iUpdatePOKEY = -1;
int iLongestPlay = -1;
// map 7-bit MIDI velocity to 4-bit POKEY volume
byte volume = midiVelocity >> 3;
// scan through the 4 channels
for(int i=0;i<4;++i)
{
// incremement play duration counter for this
// channel. we use this counter to detect which
// note has been playing longest if we need to
// steal a channel
chan[i].count++;
// check if the note is already playing on channel
if(chan[i].midiNote == midiNote)
{
iAlreadyPlaying = i;
}
// else is channel spare?
else if(!chan[i].midiNote)
{
iFree = i;
}
// else is channel the longest playing channel?
else if(chan[i].count > iLongestPlay)
{
iLongestPlay = chan[i].count;
iSteal = i;
}
}
// already got a channel playing this note?
if(iAlreadyPlaying > 0 )
{
// need to stop a note?
if(!volume)
{
// turn a note off
chan[iAlreadyPlaying].midiNote = 0;
chan[iAlreadyPlaying].note = 0;
chan[iAlreadyPlaying].volume = 0;
chan[iAlreadyPlaying].count = 0;
iUpdatePOKEY = iAlreadyPlaying;
}
}
// else check we have a nonzero volume. We will ignore
// zero volume requests against any note that is not already
// playing
else if(volume>0)
{
// convert from MIDI note to index in the notes[] array
byte note = midiNote;
while(note<37) note+=12; // 37 is lowest MIDI note we map
while(note>84) note-=12; // 84 is highest MIDI note we map
note-=37; // convert to array index value
// got a free channel?
if(iFree>0)
{
// use it
chan[iFree].midiNote = midiNote;
chan[iFree].note = notes[note];
chan[iFree].volume = volume;
chan[iFree].count = 0;
iUpdatePOKEY = iFree;
}
// else steal a channel from another note
else if(iSteal>0)
{
chan[iSteal].midiNote = midiNote;
chan[iSteal].note = notes[note];
chan[iSteal].volume = volume;
chan[iSteal].count = 0;
iUpdatePOKEY = iSteal;
}
}
// do we need to tell the POKEY anything?
if(iUpdatePOKEY > 0)
{
// make it so!
writePokey(0 + iUpdatePOKEY*2, chan[iUpdatePOKEY].note);
writePokey(1 + iUpdatePOKEY*2, POKEY_SOUNDMODE|chan[iUpdatePOKEY].volume);
}
}
void main()
{
// osc control / 8MHz / internal
osccon = 0b01110001;
// timer0... configure source and prescaler
cmcon0 = 7;
// configure io
trisa = 0b00010000;
trisc = 0b00110000;
ansel = 0b00000000;
// initialise MIDI comms
init_usart();
// reset the POKEY
resetPokey();
// loop forever
for(;;)
{
// get next MIDI note
byte msg = receiveMessage();
// handle note on/off (transpose down 1 octave)
if(MIDIMSG_NOTEON == MIDIMSG(msg))
handleNote(midiParams[0]-12, midiParams[1]);
else if(MIDIMSG_NOTEOFF == MIDIMSG(msg))
handleNote(midiParams[0]-12, 0);
}
}
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment