Modifying Auduino Code: Wrestling with RAM

Many of you may be familiar with the Auduino (Google Code Archive - Long-term storage for Google Code Project Hosting.) written by Peter Knight. I've built one recently, and I'd like to be able to easily select between keys and various scales in each key.

I was very proud of myself when coming from next to no programming experience I was able to figure out how to assign a potentiometer to the task of cycling through all 12 major scales. Because each note value is found by popping the frequency (Hz) into a formula, and the end result a bit abstract I originally had 128 variables assigning each note value to its name. This quickly ate up all the RAM, so I bit the bullet and devised a system using a excel to write out my scale maps.

auduino with key select - Pastebin.com (example of my code with all the note variables in place)
Auduino with key select (WIP) - Pastebin.com (example of my code without note variables)

The next issue I ran into was when I attempted to add 24 more scale maps (12 half whole diminished scales and 12 pentatonic minor scales). This once again caused issues. When commenting off 5 of the scales everything works, so I'm just over the memory limit.

After some poking around someone told me about PROGMEM in the <avr/pgmspace.h> library, which stores variables in flash memory until needed. I applied this to my scale map variables and got some bizarre results. If applied to all variables in the map, the output is about 3 octaves higher in pitch and really quiet. Applied to just one variable in the map (didn't seem to matter which one) and it was all the correct volume, but scales were detuned and did not go up and down predictably (every few notes would be way off for some reason). Obviously this is due to the added time it takes to send the variable to RAM, but it sort of makes using this thing for musical purposes impossible.

Auduino with key select (WIP) - Pastebin.com (example of my code with PROGMEM on one variable per map)

Has anyone ever dealt with issues like this? Any suggestions for me? I'm still new, but I've gotten this far. I would hate to hit a brick wall now. If I need to somehow declare these maps in another way I will give it a try. Perhaps my implementation of PROGMEM is off as well.

Yes, I have something very similar I will dig up and send to you this evening.

Its basically the 128 midi notes stored in progmem as the phase increment required to play the pitch on an Auduino or similar interrupt driven synth.

In addition I have another progmem map which holds the valid notes from given scales - at the moment I only have the pentatonic scale, but the code works very well so I do not see any problems with you adding more scales.

PM Me if I forget to send it to you

Here are some Auduinos for you to enjoy -

Here my code in action in the second video (which sounds like an auduino but is using my modular synth code) -

Duane B

rcarduino.blogspot.com

From your code:

// Key of C ........C D E F G A B
uint16_t majorC [29] = {  
    274, 308, 346, 366, 411, 461, 518,
    549, 616, 691, 732, 822, 923, 1036,
    1097, 1232, 1383, 1465, 1644, 1845, 2071,
    2195, 2463, 2765, 2930, 3288, 3691, 4143, 4389
       };
 
uint16_t mapMajorC(uint16_t input) {
  PROGMEM prog_uint8_t value = (1023-input) / (1024/29);
  return (majorC[value]);
}

You have the PROGMEM in completely the wrong place. What you need is something like this:

// Key of C ........C D E F G A B
PROGMEM prog_uint16_t majorC [29] = {  
    274, 308, 346, 366, 411, 461, 518,
    549, 616, 691, 732, 822, 923, 1036,
    1097, 1232, 1383, 1465, 1644, 1845, 2071,
    2195, 2463, 2765, 2930, 3288, 3691, 4143, 4389
       };
 
uint16_t mapMajorC(uint16_t input) {
  uint16_t value = (1023-input) / (1024/29);
  return pgm_read_word_near(&majorC[value]);
}

However, if you are using the equal tempered scale, why not have just one table giving the frequency for each note, then play scales by moving up/down the required number of semitones from the starting note?

dc42:
However, if you are using the equal tempered scale, why not have just one table giving the frequency for each note, then play scales by moving up/down the required number of semitones from the starting note?

Musical notes don't map neatly to integer frequencies in Hz. You can get more accuracy by having a tab;e which covers the whole musical range.

fungus:

dc42:
However, if you are using the equal tempered scale, why not have just one table giving the frequency for each note, then play scales by moving up/down the required number of semitones from the starting note?

Musical notes don't map neatly to integer frequencies in Hz. You can get more accuracy by having a tab;e which covers the whole musical range.

Maybe you misunderstood me, but that's precisely what I suggested.

Chaps,
I have already sent the OP a table of notes - in phaseincrement form which is ultimatley what an interrupt driven synth like the Auduino needs.

This is then indexed by another table for each scale. Both tables are stored in progmem and accessed through two helper functions - like so -

#ifndef _NOTETABLES_
#define _NOTETABLES_

#include "avr/pgmspace.h"

#define MIDI_NOTES 128
// used to convert midi note numbers into the increments required to generate the note in the ISR
PROGMEM unsigned int midiNoteToWavePhaseIncrement[MIDI_NOTES] =
{
 66 // 0,8.18,66.98,66                C
,70 // 1,8.66,70.96,70
,75 // 2,9.18,75.18,75
,79 // 3,9.72,79.65,79
,84 // 4,10.30,84.38,84
,89 // 5,10.91,89.40,89
,94 // 6,11.56,94.72,94
,100 // 7,12.25,100.35,100
,106 // 8,12.98,106.32,106
,112 // 9,13.75,112.64,112
,119 // 10,14.57,119.34,119
,126 // 11,15.43,126.43,126
,133 // 12,16.35,133.95,133            C
,141 // 13,17.32,141.92,141
,150 // 14,18.35,150.35,150
,159 // 15,19.45,159.29,159
,168 // 16,20.60,168.77,168
,178 // 17,21.83,178.80,178
,189 // 18,23.12,189.43,189
,200 // 19,24.50,200.70,200
,212 // 20,25.96,212.63,212
,225 // 21,27.50,225.28,225
,238 // 22,29.14,238.67,238
,252 // 23,30.87,252.86,252
,267 // 24,32.70,267.90,267           C
,283 // 25,34.65,283.83,283
,300 // 26,36.71,300.71,300
,318 // 27,38.89,318.59,318
,337 // 28,41.20,337.53,337
,357 // 29,43.65,357.60,357
,378 // 30,46.25,378.87,378
,401 // 31,49.00,401.40,401
,425 // 32,51.91,425.27,425
,450 // 33,55.00,450.55,450
,477 // 34,58.27,477.34,477
,505 // 35,61.74,505.73,505
,535 // 36,65.41,535.80,535           C
,567 // 37,69.30,567.66,567
,601 // 38,73.42,601.42,601
,637 // 39,77.78,637.18,637
,675 // 40,82.41,675.07,675
,715 // 41,87.31,715.21,715
,757 // 42,92.50,757.74,757
,802 // 43,98.00,802.79,802
,850 // 44,103.83,850.53,850
,901 // 45,110.00,901.11,901
,954 // 46,116.54,954.69,954
,1011 // 47,123.47,1011.46,1011       C
,1071 // 48,130.81,1071.60,1071
,1135 // 49,138.59,1135.32,1135
,1202 // 50,146.83,1202.83,1202
,1274 // 51,155.56,1274.36,1274
,1350 // 52,164.81,1350.13,1350
,1430 // 53,174.61,1430.42,1430
,1515 // 54,185.00,1515.47,1515
,1605 // 55,196.00,1605.59,1605
,1701 // 56,207.65,1701.06,1701
,1802 // 57,220.00,1802.21,1802
,1909 // 58,233.08,1909.38,1909
,2022 // 59,246.94,2022.92,2022
,2143 // 60,261.63,2143.20,2143       C
,2270 // 61,277.18,2270.64,2270
,2405 // 62,293.66,2405.66,2405
,2548 // 63,311.13,2548.71,2548
,2700 // 64,329.63,2700.27,2700
,2860 // 65,349.23,2860.83,2860
,3030 // 66,369.99,3030.95,3030
,3211 // 67,392.00,3211.18,3211
,3402 // 68,415.30,3402.12,3402
,3604 // 69,440.00,3604.42,3604
,3818 // 70,466.16,3818.75,3818
,4045 // 71,493.88,4045.83,4045
,4286 // 72,523.25,4286.41,4286      C
,4541 // 73,554.37,4541.29,4541
,4811 // 74,587.33,4811.33,4811
,5097 // 75,622.25,5097.42,5097
,5400 // 76,659.26,5400.53,5400
,5721 // 77,698.46,5721.67,5721
,6061 // 78,739.99,6061.89,6061
,6422 // 79,783.99,6422.36,6422
,6804 // 80,830.61,6804.25,6804
,7208 // 81,880.00,7208.85,7208
,7637 // 82,932.33,7637.51,7637
,8091 // 83,987.77,8091.66,8091
,8572 // 84,1046.50,8572.82,8572     C
,9082 // 85,1108.73,9082.58,9082
,9622 // 86,1174.66,9622.66,9622
,10194 // 87,1244.51,10194.85,10194
,10801 // 88,1318.51,10801.07,10801
,11443 // 89,1396.91,11443.33,11443
,12123 // 90,1479.98,12123.79,12123
,12844 // 91,1567.98,12844.71,12844
,13608 // 92,1661.22,13608.50,13608
,14417 // 93,1760.00,14417.70,14417
,15275 // 94,1864.65,15275.02,15275
,16183 // 95,1975.53,16183.31,16183
,17145 // 96,2093.00,17145.63,17145     C
,18165 // 97,2217.46,18165.16,18165
,19245 // 98,2349.32,19245.31,19245
,20389 // 99,2489.01,20389.70,20389
,21602 // 100,2637.02,21602.14,21602
,22886 // 101,2793.83,22886.67,22886
,24247 // 102,2959.95,24247.58,24247
,25689 // 103,3135.96,25689.42,25689
,27216 // 104,3322.44,27216.99,27216
,28835 // 105,3520.00,28835.39,28835
,30550 // 106,3729.31,30550.04,30550
,32366 // 107,3951.06,32366.63,32366
,34291 // 108,4186.01,34291.26,34291    C
,36330 // 109,4434.92,36330.32,36330
,38490 // 110,4698.64,38490.65,38490
,40779 // 111,4978.03,40779.41,40779
,43204 // 112,5274.04,43204.25,43204
,45773 // 113,5587.65,45773.32,45773
,48495 // 114,5919.91,48495.14,48495
,51378 // 115,6271.92,51378.79,51378
,54433 // 116,6644.87,54433.96,54433
,57670 // 117,7040.00,57670.76,57670
,61100 // 118,7458.62,61100.07,61100
,64733 // 119,7902.13,64733.26,64733
,3046 // 120,8372.02,68582.53,3046      C
,7124 // 121,8869.84,72660.64,7124
,11445 // 122,9397.27,76981.30,11445
,16022 // 123,9956.06,81558.77,16022
,20872 // 124,10548.07,86408.50,20872
,26010 // 125,11175.30,91546.65,26010
,31454 // 126,11839.81,96990.28,31454
,31454 // 127,11839.81,96990.28,31454 // this is wrong, need to calculate correct value, even though at 8Khz its wrapping around on every tick
};

// Pentatonic scale
// C D E G A C
// to map to midi note

#define PENTATONIC_NOTES 54

// provides an index of pentatonic notes in the midi note table

PROGMEM unsigned char sPentatonicNotes[PENTATONIC_NOTES] =
{
  0,   2,  4,  7,  9, 
  12, 14, 16, 19, 21, 
  24, 26, 28, 31, 33,
  36, 38, 40, 43, 45,
  48, 50, 52, 55, 57,
  60, 62, 64, 67, 69,
  72, 74, 76, 79, 81,
  84, 86, 88, 91, 93,
  96, 98,100,103,105,
 108,110,112,115,117,
 120,122,124,127 
};

unsigned int getMidiNotePhaseIncrement(unsigned char sNote)
{
 return pgm_read_word(midiNoteToWavePhaseIncrement + (sNote));
}

// Duane B 08/11/2012
// change the function to take an unmapped analog input
// and do the mapping to the note range of the scale inside the function
// instead of in loop
unsigned int getPentatonicPhaseIncrement(unsigned int nAnalogInput)
{
 // map the analog input value from 0,1024 to 0 to the number of notes in our scale
 uint8_t sPentatonicNote = map(nAnalogInput,0,1024,0,PENTATONIC_NOTES);

 uint8_t sMidiIndex = pgm_read_byte(sPentatonicNotes + sPentatonicNote);
 return pgm_read_word(midiNoteToWavePhaseIncrement + sMidiIndex);
}

#endif

If the OP or anyone esle wants to add additional scales, post them back here and we can have a useful resource.

code in action -

Duane B

rcarduino.blogspot.com

As stated Duane kindly passed me his solutoto this issue. Tonight was spent glued to the TV so I didn't get any work done, but I look forward to trying it out in the coming days.

As for my previous use of PROGMEM I actually did have it where you suggested and it caused distortions there as well. I began experimenting with the placement after a while, and that's what I ended up posting.

Thanks for the replies.

Hi,
Just a quick note to mention that I have updated the code previously posted. You will see the update as follows in the previous post -

// Duane B 08/11/2012
// change the function to take an unmapped analog input
// and do the mapping to the note range of the scale inside the function
// instead of in loop
unsigned int getPentatonicPhaseIncrement(unsigned int nAnalogInput)
{
 // map the analog input value from 0,1024 to 0 to the number of notes in our scale
 uint8_t sPentatonicNote = map(nAnalogInput,0,1024,0,PENTATONIC_NOTES);

 uint8_t sMidiIndex = pgm_read_byte(sPentatonicNotes + sPentatonicNote);
 return pgm_read_word(midiNoteToWavePhaseIncrement + sMidiIndex);
}

As you can see the getPentatonicPhaseIncrement now accepts a value from an analogInput i.e. 0 to 1024

unsigned int getPentatonicPhaseIncrement(unsigned int nAnalogInput)

and internally does the mapping to the number of notes in the given scale -

 uint8_t sPentatonicNote = map(nAnalogInput,0,1024,0,PENTATONIC_NOTES);

which it then uses to acces the PROGMEM array which maps from scale note number to midi note number

Which it then uses to fetch the required phase increment for play back at an 8Mhz sample rate which is the interrupt rate of the Auduino, IllutronB and most other Arduino based synths.

I have not had chance to test this, but the logic is right, if anyone is able to test and finds a problem, let me know.

Duane B

OK... I've been mildly successful with blending your's and Peter Knight's code. There some strange things I don't quite understand though

Pastebin link to save post space - Auduino with RCArduino's additions - Pastebin.com

Starting with the stock unmodified Auduino file, I've pasted yours in just before the other maps are declared (since the log map is still used by the two grain freq knobs I left them untouched). I then gave the loop getPentatonicPhaseIncrement to analog read the SYNC_CONTROL pot (analog input 4). For playability I commented off the highs and lows to a 3 octave range on the pentatonic map and told the code PENTATONIC_NOTES contains 16 entries.

It seems to play fine now, but the bizarre thing I see is that the pot on analog pin 1 which is for grain 2's decay seems to mirror analog pin 4. Why? No clue. It's not my circuit, as I can upload my previous code and it seems to behave correctly. I don't see why the code is causing this, as I didn't do anything different to the GRAIN2_DECAY_CONTROL variable (it only appears once to be declared and once to be read... neither were touched). As far as I can see it should still only affect the amplitude of grain 2.

The second thing I don't quite understand is what the nAnalogInput variable is doing. If this is an input should it be assigned to a pin? I actually tried this (assigned to pin 4), and got tons of errors (as I assumed I would... stabbing in the dark).

I haven't attempted to add more scales to test the memory issues at this time... I want to be sure everything is working correctly before building on what I've got. Sorry if these are silly novice questions. I'm still learning. The vanilla code was a lot less complicated than what we're doing here.

Hi,
One thing you can do is follow this comment I added to the google code group -

Hi, Yes I can confirm that the fix is to simply to change the first few lines from this -

uint16_t syncPhaseAcc; uint16_t syncPhaseInc; uint16_t grainPhaseAcc; uint16_t grainPhaseInc; uint16_t grainAmp; uint8_t grainDecay; uint16_t grain2PhaseAcc; uint16_t grain2PhaseInc; uint16_t grain2Amp; uint8_t grain2Decay;

to this -

uint16_t syncPhaseAcc; volatile uint16_t syncPhaseInc; uint16_t grainPhaseAcc; volatile uint16_t grainPhaseInc; uint16_t grainAmp; volatile uint8_t grainDecay; uint16_t grain2PhaseAcc; volatile uint16_t grain2PhaseInc; uint16_t grain2Amp; volatile uint8_t grain2Decay;

All that we are doing is adding the volatile keyword to the variables which are accessed by both the interrupt service routine and loop. This prevents the compiler from optimising them. Without this keyword, the compiler will optimise the variables inside the interrupt service routine so that they are no longer updated by loop.

Tested in Arduino 1.0

Duane B

rcarduino.blogspot.com

It will stop the compiler from doing unexpected things as you progress your project.

With regards to the grain decay - it didn't made much of a difference in the sound of any Auduino's I built - the synch phase, and the two grain phase increments have the most noticeable effect. Next time I build one, I will probably use the two phase decay controls as output gates instead - one for gate wave form and the other for frequency.

Duane B.

Duane, you're awesome.

It's working pretty well. I still want to sit down with a tuner to make sure it's producing the correct notes, and possibly change the range I'm outputting, but as of now I've got 36 different scale maps that are pulling up as needed, and RAM isn't getting overloaded.

EDIT - I should point out that the scales I've got are all major (despite their labeling to the contrary). I haven't had time to write in the diminished and pentatonic scales.

Hi,

I have updated Peter Knights original Auduino code to include the fix for Arduino 1.0 and later and also added a delay effect you might want to try out, its here -

I am also planning on building a version of the five dollar synthesizer which can be run on the same circuit as Arduino, for that I wanted some arpeggios - do you have any I can try out ?

Duane B

rcarduino.blogspot.com

If you're playing with arpeggios you're going to be playing the notes in a chord. Pretty much all standard chords can be found here:

If you've got a piano or keyboard (virtual or not), play around with them and find something you like. Usually an arp function on a synth will play the notes in sequence traveling up, down, up and back down (with and without a repeat of the top and bottom notes), and random.

I almost wrote that giving an instrument just a few chords or just one key seriously diminishes its usefulness, but then I remembered you're building this thing for $5 and probably just for fun. Just find some notes that sound pretty together and go nuts. Post a vid when its done.

As for my progress, I've not made any. Between work and study the Auduino has been on hold, and will probably remain so until after the holiday (I'm in America). The damn thing still keeps me up at night thinking of solutions or features, so I'm still excited about it. I hope to have a fully working prototype soon. As it turns out I'm short by one analog input, so I think I'll get myself a Sparkfun Pro Mini 328 for the final product.

https://www.sparkfun.com/products/11113?

This bad boy is smaller, and has 8 analog ins. Seems perfect for the final product. I'm also considering designs for the case. I think I'll be designing something to be built through Shapeways, since they're rather affordable and I'll be able to get exactly what I want.

Hi,

The keyboard allows you to capture any set of keys as an arpeggio, but as the sound is really good I would like to make it more accessible to people that will not be comfortable hacking a keyboard. I will have a look at the chords but I am starting to think the best option is to design it around having twelve buttons or keys for chord capture and play back with a pot for transpose.

As for additional analogue inputs I bought some 4051 analogue multiplexers a while back. I haven't used them yet, but they get you a lot of extra inputs.

Thanks
Duane.

New features, new set of issues. I've gotten myself an RGB 16x2 character LCD from Adafruit, and I've attempted to combine the sketch from adafruit's tutorial with my own. Before making the LCD do anything useful, I want to make it play nice with what I've got.

Here is Adafruit's tutorial code: Adafruit's tutorial: http://learn.adafruit.com/character...gb-backlit-lcds

Here's what my code currently looks like: Auduino RGB LCD - Pastebin.com

The LCD seems to work exactly as expected, but the Auduino doesn't work. I'll get a seemingly random tone, that holds steady and changes to a different tone every time the RGB on the LCD cycles back to blue.

Both my Auduino sketch, and the sketch from the tutorial work correctly independently of each other, so I know its not my wiring. After spending an afternoon fiddling with the code I've determined the issue is caused by the PWM section for the backlight in the loop.

for (int i = 0; i < 255; i++) {
    setBacklight(i, 0, 255-i);
    delay(1);
  }
  for (int i = 0; i < 255; i++) {
    setBacklight(255-i, i, 0);
    delay(1);
  }
  for (int i = 0; i < 255; i++) {
    setBacklight(0, 255-i, i);
    delay(1);
  }

For some reason this is interfering with the interrupt. I assumed at first it was the delay causing my problems, but according to the arduino knowledge base, delay does not affect interrupts. My working hypothesis now is that I've run into an issue with the UNO's timers. Before I go and upgrade to a more expensive board I was hoping someone could take a look and see if they know a way around this.

In case anyone was wondering, yes I want to make my LCD rapidly change colors in a seizure inducing fashion... the case I've got designed for this is really colorful. I want the thing to look fun and inviting to play.

EDIT - OK... so if this is some sort of hardware limitation issue (and I'm thinking that's the case) I'm just going to program a second AVR chip to handle just the PWM. That way I can be sure it's not eating up cycles or messing with the synthy goodness. I've already got to run a power circuit to run the arduino and my echo board, so it won't be a big deal.

There are three counter/timers in the atmega328 (more on the chip used in the Mega) and from your description, it appears that the Auduino code uses one or more of them. Each counter/timer controls two of the PWM pins. When you are using a counter/timer for something else, you can't use its associated PWM pins.

I suggest you temporarily modify setBacklight() to write to just one PWM pin, and thereby establish which PWM pin(s) cause the problem. Then try switching those to the spare PWM pins, assuming you have some.

Alternatively, look at the Auduino code to see which counter/timer(s) it is using, then you can work out which PWM pins are available.

Hi,
The Auduino uses Timer1 for scheduling the ISR and Timer2 for generating the PWM, Timer0 is untouched and free for you to use, you should also be able to use timer2 output A.

So, 5,6,11 look like a good starting point.

Duane B

for anyone who cares, I've pretty much finished my code for this project. I may go back and tweak the range of the scales, and add a button that has to be pressed to allow audio out (right now it's a drone instrument).

(Nearly) finished product: Auduino with working LCD readout - Pastebin.com

The LCD PWM is a problem that I'm going to solve with a separate AVR chip... it's a pretty cheap fix, and it's something I want to try anyway. That coupled with the headaches I foresee figuring out timers, and the fact that I'm already getting strange audio effects as my code grows (which sound cool at this point, but much more could ruin the instrument), I don't want this to get much larger.

Right now the LCD displays which scale is selected, and the note that's currently playing. This helps because without the LCD readout it's really hard to know where the hell you are and what you're playing. I want to be able to actually use this thing.

Looks like you have done a lot of work there, if your not using additional external effects for performances you should take a look at the delay code here -

But something that might be more interesting is that I have now added trigger to the oscillators in the five dollar synth, what this means is that I can use the keyboard to trigger the other oscillators leading to something very similar sounding to the Auduino with keyboard pitch control -

The keyboard code needs a complete rewrite but the four oscilator synth engine is now pretty capable.

Duane B

That's really rad Duane. I may take a stab at something similar in the future.

As for the echo, I'm using a Wooster Audio Space Baby. It's an echo that I absolutely love, and I started this project looking for a way to get better use of it.

I think the next step for your $5 keyboard is to get the whole thing into its own custom enclosure to make it look complete. The sounds it makes are really awesome.