Wednesday, 30 June 2010

DIY Boss PC-2 / Amdek PCK100 Analog Percussion Synth

Back at the end of the 80's I went in a music shop in London and on impulse I bought this wierd BOSS box that made weird bloopy noises... a PC-2 Percussion Synthesizer. It wasn't a difficult decision at the time since it only cost me £10 brand new boxed (I think they were trying to get rid of them) and it came with a free brand new BOSS HC-2 Hand Clapper pedal, although that seemed even less useful (though the guy did a good sales pitch involving a tale about a friend with no arms who had trouble applauding at gigs... :o)


Apart from a bit of novelty value I gotta be honest they didn't get a lot of use... I think the HC-2 got chucked during a house cleanout and the PC-2 was sold on ebay a few years back (and was amazed to get £100 for it). Well now I am kicking myself and wished I'd held on to these two collectables.

So when I saw the schematic of the Amdek PCK100 online (actually a kit form of the PC-2 sold by a Roland affiliate) I decided to try to make one and once again re-live boingy noise heaven.


On the page http://www.effectsdatabase.com/model/amdek/pck100 where I found the schematic I also found a photo of the PCB and decided to try to use it directly. With some manipulation in Paint Shop Pro and the use of Press'n'Peel PCB etching film I was able to make a copy of the board and after working out some workarounds (e.g. using BA6110 instead of ultra-rare BA662A VCA chip) I actually got it to work.. So in case you're interested ..here is how I did it

1) I started off with this photo of the track side of the original PCB

2) Turned to mono, upped the contrast, then very carefully use the "eraser" tool to ensure there are good clean gaps between all the tracks

3) Marked drill holes with circles

4) Drop colour depth to 2 colours

5) Negative image and a few embellishments and its ready to press'n'peel

6) Laser printed etch-resist transferred to copper clad board using a hot iron

7) Etched, drilled and trimmed ready for components

I mounted the board inside a project box from Maplin. By the way I have a thing for Dymo embossed label tape :)





The Amdek user guide gives info on some mods to the board (VCO wave form change, mod waveform change) which I added toggle switches for. Usually the Sweep control is a center tap pot.. I didn't have one so I instead rigged up a DPDT toggle and a resistor to a normal pot so that the same effect could be acheived (although I am not sure it works so well)

The original board calls for a Roland BA662A VCA chip... you won't find one! you can use a similar BA6110 chip but the pinout is different. I found the following worked

  • socket 1 connect to BA6110 pin# 4
  • socket 2 connect to BA6110 pin# 2
  • socket 3 connect to BA6110 pin# 1
  • socket 4 no connection. BA6110 pin#3 connected to GND (pin 5)
  • socket 5 connect to BA6110 pin#5
  • socket 6 connect to BA6110 pin#6
  • socket 7 connect to BA6110 pin#7
  • socket 8 connect to BA6110 pin#8
  • socket 9 connect to BA6110 pin#9
Click below to get the full size PCB template to use directly. See the link at start of article for schematic, photo of top of the board and original PCK100 kit instructions



Tuesday, 11 May 2010

Battlezone with lasers

This is a project I've had simmering on the back burner for a while. Still at the early stages but thought it might be fun to keep track of each step here

A few months back I got a 20kps laser scanner galvo set off ebay with the intention of making my own laser projector and a vision of using it to play some old vector arcade games... particularly my old fave Atari Battlezone. The arcade game bit seemed pretty easy, since you can play BZ on the open source MAME emulator so I thought I could hook into the vector terminal emulation.

I found the asynchronous UART on an Arduino board was not quite fast enough to cope with the data... dropping bits all over the place, so I started looking at a USB conneciton to a PIC2455. As a SourceBoost C user I was not able to find any easy to understand USB CDC (Communication Device Class, a.k.a serial port) implementations for the PIC - so I decided to make my own, leaning heavily on sample code I found online.

Well I finally got to the point where my PIC would connect via USB show up as a COM port and be easy to access from a Windows program. Then I hooked up an 12-bit SPI dual DAC and connected it to the galvo setup and tried the first random hacking into MAMEs vector module.

I didn't expect it to work first time, and didn't! but my impatient hacking did produce some interesting squiggles at about 2 fps. I needed to use a long exposure photograph to actually make sense of it, but eventually I recognised a couple of parts of the display and got quite excited that the concept was proved!

The coordinate handling is obviously messed up and the image is wrapping on itself multiple times, also there is no attempt at blanking yet - so there are stray lines all over. The big job will be to find some way to optimise the render list to stop throwing the galvos all over the place and improve on the 2 fps refresh!

As you can see I have a long way to go!

Here is the plot showing the bits I recognised


Here is an actual MAME screen showing what it should look like


If things improve I will post an update!

Monday, 3 May 2010

Motion detection to midi with puredata

another experiment with puredata, webcam image is passed through pix_movement object and a pix_blob turns the result into two midi note streams which are sent into reason via midi yoke. the image is the output of the pix_movement (difference between frames)



I put the pd patch at http://sites.google.com/site/skriyl/Home/pd-projects (motion noise.pd)



The patch outputs on midi channels 1 and 2. I used midi yoke and PD's midi output, then piped this into propellerheads reason, where you can use the "advanced midi" to set up midi bus A then lock down channels 1 and 2 to specific instruments in the rack. I used an NNXT with glockenspiel patch and nn19 with strings patch

Sunday, 2 May 2010

First play with PureData

I was thinking about making a midi "harp" and thought of trying with a webcam, using puredata to analyse the images and generate midi/sounds. This is the first time I have played with PD and I don't really know what I am doing with it yet... you'll see I am not quite there with the midi harp yet, but I am impressed with how quickly you can get something fun working in PD



Here is the PD sketch, which I got to by hacking about with one of the GEM tutorial sketches

Thursday, 22 April 2010

Matrix feedback in Reason

How to do something like this...



A great thing about Reason is that it allows you to wire a continuous input (like a CV, MIDI CC#, Mod wheel etc) to pattern changes on something like the Matrix sequencer or Redrum. You can't do this directly (there is no pattern change CV input) but you can do it via the "programmer" in the combinator. This is a great thing to experiment with...especially if the patterns are different beat lengths/step values and you layer a few of them . In one of my other clips I used MIDI CC# signals generated by a Lavalamp to randomly switch patterns on a set of Redrum modules.

In the above above clip I have 3 matrix sequences driving each other, which can result in some random sounding patterns which repeat over long periods and can descend into chaos with one small tweak.. eventually arriving at a new repeating cycle (of course you need to drive one or more sound modules like NNXT etc with the matrix outputs to be able to hear anything...)

Start by making a Combinator...

Inside the combinator, create 3 matrix sequencers.

Wire Curve CV output from each Matrix Rotary inputs 1,2,3 on the combinator. Ensure these are only connections between matrixes (-ices?) and combinator

Click the "show programmer" button. Click on Matrix 1 and next to "Rotary 2" source select "Pattern Select" target. For Matrix 2 map Rotary 3 to pattern select, For Matrix 3 map Rotary 1 to pattern select

Flip to Curve view on each matrix and draw a few random curves (Randomize pattern option can be good). Start the Matrixes and click randomly on their pattern screens. Soon they should be flicking between patterns almost at random

Wire the Gate and Note CV outputs of one or more matrix to a sound module such as NNXT.


Flip the matrixes to note mode and click at random on the screens. Change pattern lengths and note resolutions, try the "randomize pattern" option. After a while things should be getting pretty freaked out

Tuesday, 23 March 2010

Pre programmed PICs

Following some requests, I have listed pre-programmed PICs on ebay for a couple of my projects. If there is much interest (and its not all a massive hassle) I might also look into getting some PCBs made up and putting kits together
For now here are the PICs http://cgi.ebay.co.uk/ws/eBayISAPI.dll?ViewItem&item=150426687494

POKEY sound chip experiments

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

}

}