/*
Nebulophone v01 by Dr. Bleep for Handmade Music Austin #4
bleeplabs.com for schematic and more info
handmademusic.noisepages.com for handmae music events
Sources for setting up timers.
http://www.cs.mun.ca/~rod/Winter2007/4723/notes/timer0/timer0.html
http://arcfn.com/2009/07/secrets-of-arduino-pwm.html
http://blog.wingedvictorydesign.com/2009/05/29/generate-real-time-audio-on-the-arduino-using-pulse-code-modulation/2/
The Nebulophone is a simple synthesizer designed for the Andromeda Space Rockers series.
Nebulophone01 :
- Five waveforms with adustable decay
- Analog Lowpass filter contolled by photocell
- LFO modulation of filter through LED
- Arpeggiator that can be linked to IR clock rate
- 10 keys played by alligator clip stylus. Can be set to 3 ranges in major or chromatic temperment.
Future additions? :
- Multiplexer chip to add more contols and inputs.
- Isolate LEDs to reduce output noise.
- Fix IR clocking bugs
- Pitch modulation
- Attack controls
- Audio input for modulation
*/
// Here's the wavetable. It contains four differnt waveforms(ramp, tri, squ, and pluse) that are selected by adding
// an offest (waveselect). You could have differnt tables but this allows you to move around between different
// waveforms with the "waveselect" variable, though that is not implemented in this code. More waveforms could be added but
// the pot used to select them is already divided into some tiny slices.
int wavetable[]= {
0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96,104,112,120
,128,136,144,152,160,168,176,184,192,200,208,216,224,232,240,248
, 0, 16, 32, 48, 64, 80, 96,112,128,144,160,176,192,218,234,250
,255,250,234,218,192,176,160,144,128,112, 96, 80, 64, 48, 32, 16
, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 0
, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255,255,255,255, 0
};
//Values for a chromatic scale. These we determined by hooking the output to a tuner
int chromatic[] = {
1012, 954, 899, 850, 802, 759, 716, 674, 636, 600, 566, 536, 506
, 478, 450, 426, 399, 377, 356, 336, 316, 300, 283, 268, 253, 239
, 225, 213, 199, 188, 178, 168, 158, 150, 141, 134, 126, 120, 113
};
//Values for C major
int major[] = {
1012, 954, 850, 759, 716, 636, 566, 506, 478, 426, 377, 356, 316
, 283, 253, 239, 213, 188, 178, 158, 141, 126, 119, 106, 94, 89
};
//The keyboard pads. Since they wen't all in order or on just one port, this allows you to scan them in order easily.
int pin[]={
9,10,12,0,2,4,5,6,7,8
};
// And the rest. More on individual variables as they come up.
int irc=0;
int osc=1;
int wave;
int wavec=255;
int waveindex = 2;
int n =1;
int frequency = 850;
int keypress;
int prevn;
int prevkeypress;
int attack;
int waveselect;
int r;
int i=1;
long prev;
long prev2;
long prev3;
long prev4;
long prev5;
long prev6;
long prev7;
long prev8;
int release=256;
int releaselength =20;
int wavepot;
int envpot;
int lfopot;
int irin;
int irout;
long irtempo;
int ir=0;
long previrtempo=1;
long previr=0;
int arppot;
int arpselect;
int count;
int a;
int key =1;
int d;
int s;
int lforate;
int lforateC;
int lfo =0;
int l=0;
int j=10;
int r2;
int r3;
int irtempomod;
int irswitch;
int arprate =10;
int arprateF;
int arprateL;
int keyoffset;
int attacklength = 30;
int scan=1;
int shiftbutton;
int prevshiftbutton;
int shift=0;
int octaveselect = 1;
int octaveselectpot = 1;
int scaleselectpot = 1;
int maxrelease =64;
int maxbrightness =200;
int beat;
int keym;
int prevarprateF;
void setup() {
randomSeed(analogRead(2));
// Leave serial off if you want to be in tune or get fast arpeggios and lfo
//Serial.begin(9600);
// The key pins
pinMode(0, INPUT);
pinMode(2, INPUT);
pinMode(4, INPUT);
pinMode(5, INPUT);
pinMode(6, INPUT);
pinMode(7, INPUT);
pinMode(8, INPUT);
pinMode(9, INPUT);
pinMode(10, INPUT);
pinMode(12, INPUT);
pinMode(14, INPUT);
pinMode(15, INPUT);
pinMode(3,OUTPUT); // PWM audio out
pinMode(13, OUTPUT); // IR out
pinMode(11, OUTPUT); // LFO LED out
// Turn on pullups for all keys
digitalWrite(0, HIGH);
digitalWrite(2, HIGH);
digitalWrite(4, HIGH);
digitalWrite(5, HIGH);
digitalWrite(6, HIGH);
digitalWrite(7, HIGH);
digitalWrite(8, HIGH);
digitalWrite(9, HIGH);
digitalWrite(10, HIGH);
digitalWrite(12, HIGH);
digitalWrite(14, HIGH);
digitalWrite(15, HIGH);
cli(); // Turn off interrupts. I did seem to have problems if this wasn't done.
// Basically what is happening is that timer1 is being used to oscillate at the scale values while stepping through
// the wavetable. The PWM wants to output at only 500Hz, which is pretty useless for audio. Here we can have it go
// all across the audio range! More info at the site listed at the top
TCCR2A = B00000011;
TCCR2B = B00000001;
TCCR1A = B00000000;
TCCR1B = B00001010;
TIMSK1 = B00000010;
sei(); // Turn on interrupts.
}
// This happens every time timer 1 overflows 8 times, as setup by the prescaler.
ISR(TIMER1_COMPA_vect) {
//Step through 32 places of the wavetable and go back to 1.
if (waveindex > 32) {
waveindex = 1;
}
waveindex ++;
// For the first for waveforms. The wave is equal to the index that is counting up each cycle of this interupt plus the offset.
// Release is handeled by simply subtracing an increading variable. This isn't a great way to decrease the volume of waveforms
// but it does get the job doe without needing alot and does produce and interesting sound
// Wave is then constrained to 8 bits so any variables outside the wavetable or release range dont crash the interupt.
if (s==1){
wave = ((wavetable[waveindex+waveselect])-release);
wavec = constrain( wave, 0, 255);
OCR1A = frequency;
}
// The noise waveform. You could call random at a differnt rate but this way makes it sound just like Atari tanks!
// if you don't add to fequency, you can crash the interupt at lower frequencies. 224 is arbitrary.
else if (s==0){
r = random(250);
wave = ((r)-release);
wavec = constrain( wave, 0, 255);
OCR1A = frequency+224;
}
analogWrite(3, wavec);
}
void loop(){
////////////////////////////////////////////////////////////////// read controls
// To keep the processor free, controls are only read every 10 milliseconds.
if (millis() - prev5 > 10 ) {
prev5 = millis();
shiftbutton = digitalRead(14);
irswitch = digitalRead(15);
r2 = random(220)*10;
wavepot = (analogRead(3));
arppot = (analogRead(5));
lfopot= (analogRead(4));
}
if (scan ==1){
n++;
if(n>9){
n=0;
}
}
else if (scan==0){
}
////////////////////////////////////////////////////////////////// Keys
// Keys are read by quickly stepping through the pins and seeing if any are low. If so,
// that pin is active and the key is applied to one of the scales of notes which is in
// turn applied to the interupt oscillator.
// If that some key the changes to high, the the release starts, decreasing the volume
// of the output. Keep in mind that the ocsillator is always going, even when no key is
// pressed, the output is simply reduced by 255, makking it silent. stoping the interupt
// would crash it.
keypress = digitalRead (pin[n]);
prevkeypress = digitalRead (pin[key]);
if (keypress == LOW){
scan=0;
key = n;
release=1;
osc=1;
}
if (prevkeypress == LOW){
release=1;
osc=1;
}
if (prevkeypress == HIGH){
scan=1;
if (millis() - prev > releaselength ) {
prev = millis();
if (release<=254){
release+=10;
}
if (release>=255){
release=255;
osc=0;
}
}
}
////////////////////////////////////////////////////////////////// shift button
// Here the shift button is read and stps through the octave the keyboard will play and
// the scale. "key" is the pin we jsut got srom scalling the keyboard, "a" is the
// arpeggiator value, and "keyoffset" is the octave.
if (shiftbutton != prevshiftbutton){
if (shiftbutton == HIGH) {
shift++;
}
else{
}
prevshiftbutton = shiftbutton;
}
frequency = constrain(frequency,100,1024);
keym = (key + a + keyoffset);
if (shift ==0){
keyoffset=1;
frequency=major[keym];
}
else if (shift ==1){
keyoffset=6;
frequency=major[keym];
}
else if (shift ==2){
keyoffset=11;
frequency=major[keym];
}
else if( shift == 3)
{
keyoffset=1;
frequency=chromatic[keym];
}
else if( shift == 4)
{
keyoffset = 10;
frequency=chromatic[keym];
}
else if( shift == 5)
{
keyoffset = 20;
frequency=chromatic[keym];
}
else if( shift >= 5){
shift=0;
}
////////////////////////////////////////////////////////////////// LFO
// The LFO outputs analog levels through the PWM in a more traditional way but since it's using
// pin 11 with is also on Timer 1, it will sound much better since the LED won't actually be
// blinking at 500 Hz.
// Only one pot is used to control mode as well as rate. Inside each mode a differnt output
// is performed based on lforateC.
if (lfopot <256){
lforate = map(lfopot, 0, 256, 255 , 1);
lforateC = constrain(lforate, 0,maxbrightness);
analogWrite(11, lforateC);
}
else if (lfopot >=256 && lfopot <512){
lforate = map(lfopot, 256, 512, 96 , 1);
lforateC = constrain(lforate, 1 , 96);
if (millis() - prev4 > lforateC ) {
prev4 = millis();
lfo+=8;
analogWrite(11, lfo);
if (lfo>=maxbrightness){
lfo=0;
}
}
}
else if (lfopot >=512 && lfopot <768){
lforate = map(lfopot, 512, 768, 255 , 1);
lforateC = constrain(lforate, 1 , 255);
if (millis() - prev4 > lforateC ) {
prev4 = millis();
if (lfo==0){
analogWrite(11, 0);
lfo=1;
}
else if (lfo>=1){
analogWrite(11, maxbrightness);
lfo=0;
}
}
}
else if (lfopot >=768&& lfopot<960){
lforate = map(lfopot, 768, 960, 255 , 1);
lforateC = constrain(lforate, 1 , 255);
if (millis() - prev4 > lforateC ) {
prev4 = millis();
analogWrite(11, r2);
}
}
else if (lfopot>=960 && lfopot<992){
if (millis() - prev4 > arprate/3 ) {
prev4 = millis();
if (lfo==0){
analogWrite(11, 0);
lfo=1;
}
else if (lfo>=1){
analogWrite(11, maxbrightness);
lfo=0;
}
}
}
else if (lfopot>=992){
if (millis() - prev4 > arprate/2 ) {
prev4 = millis();
if (lfo==0){
analogWrite(11, 0);
lfo=1;
}
else if (lfo>=1){
analogWrite(11, maxbrightness);
lfo=0;
}
}
}
////////////////////////////////////////////////////////////////// Wave select
//Just like the LFO pot except the variable inside each mode controls release.
if (wavepot <204){
releaselength = map(wavepot, 0,256,1,maxrelease);
releaselength = constrain(releaselength, 1,maxrelease);
waveselect=0;
s=1;
}
else if (wavepot >=204 && wavepot<408){
releaselength = map(wavepot, 204,408,1,maxrelease);
releaselength = constrain(releaselength, 1,maxrelease);
waveselect=32;
s=1;
}
else if (wavepot >=408 && wavepot<612){
releaselength = map(wavepot, 408,612,1,maxrelease);
releaselength = constrain(releaselength, 1,maxrelease);
waveselect=64;
s=1;
}
else if (wavepot >=612 && wavepot<816){
releaselength = map(wavepot, 612,816,1,maxrelease);
releaselength = constrain(releaselength, 1,maxrelease);
waveselect=95;
s=1;
}
else if (wavepot >=816){
releaselength = map(wavepot, 816,1024,1,maxrelease);
releaselength = constrain(releaselength, 1,maxrelease);
waveselect=0;
s=0;
}
////////////////////////////////////////////////////////////////// ARP
//Variable effects speed but is changed based on the IR switch
if (arppot <5){
count=0;
a=0;
}
if (arppot >=5 && arppot<256) {
arprateL = map(arppot, 5, 256, 319, 1);
arprateL = constrain(arprateL, 1 , 319);
arprateF = map(arppot, 5, 256, 4, 1);
arprateF = constrain(arprateL, 1 , 4);
if (count==0){
a=0;
}
else if (count==1){
a=2;
}
else if (count>=2){
count=0;
}
}
if (arppot >=256 && arppot<512) {
arprateL = map(arppot, 256, 512, 319 , 1);
arprateL = constrain(arprateL, 1 , 319);
arprateF = map(arppot, 256, 512, 1, 4);
arprateF = constrain(arprateF, 1 , 4);
if (count==0) {
a=0;
}
else if (count==1) {
a=1;
}
else if (count==2) {
a=2;
}
else if (count>=3) {
count=0;
a=1;
}
}
if (arppot >=512 && arppot<768) {
arprateL = map(arppot, 512, 768, 255 , 1);
arprateL = constrain(arprateL, 1 , 255);
arprateF = map(arppot, 512, 768, 1, 4);
arprateF = constrain(arprateF, 1 , 4);
if (count==0)
{
a=r3;
}
else if (count==1)
{
a=0;
}
else if (count>=2)
{
count=0;
}
}
if (arppot >=768) {
arprateL = map(arppot, 768, 1024, 319 , 1);
arprateL = constrain(arprateL, 1 , 319);
arprateF = map(arppot, 768, 1024, 1, 4);
arprateF = constrain(arprateF, 1 , 4);
if (count==0)
{
a=0;
}
if (count >=1 && count<8)
{
a=count;
}
else if (count>=8)
{
a=0;
count=0;
}
}
if (prevarprateF != arprateF){
previrtempo=0;
}
prevarprateF = arprateF;
////////////////////////////////////////////////////////////////// IR
// All of the Andromeda Space Rocker Kits can communicate via IR. The Nebulophone can sync it's arpeggiator to a multiple
// of the IR clock rate. This is done but simply reading the analog input conected to the detector and measuring the time
// between peaks. There are some sync issuse to work, though. I believe the rate is read correctly but sometimes the arp
// speed can lag.
irin = (analogRead(2));
if (irin < 600 ){
ir=1;
}
else if (irin >= 600 ){
ir=0;
}
if (irswitch == 0){
arprate = irtempo / arprateF;
if (ir != previr ){
irsub();
}
}
else if (irswitch == 1){
arprate = arprateL;
}
if (millis() - prev8 >= arprate ) {
r3 = random(-8,8);
prev8 = millis();
count++;
digitalWrite(13, HIGH);
}
else {
digitalWrite(13, LOW);
}
}
void irsub(){
if (ir == 1){
irtempo = millis() - previrtempo;
previrtempo = millis();
}
else{
}
previr = ir;
}