ChipKIT Sketch: Mini Polyphonic Sampling Synth

In our hands-on review of the Digilent chipKIT Uno32, we posed the question of what the lasting appeal might be for a 32-bit Arduino work-alike. We felt it needed some novel applications exploiting its special features…not just the same old Arduino sketches with MOAR BITS. After the fractal demo, we’ve hit upon something unique and fun…

So just what are the chipKIT’s unique features over a stock Arduino? Until the expected Ethernet shield ships this summer, a few ideas are on hold. Let’s see then…there’s no shortage of MIPS, of course…but there’s also heaps of RAM and flash storage. And with the latter, sampled audio came to mind. There are Arduino shields for just this sort of thing — the Adafruit Wave Shield turns up in many projects, using an SD card for sound storage — but if one’s needs are modest, the chipKIT’s PIC32 is perfectly capable of storing brief audio samples in its own flash program space, no cards, adapters or added expense required. We estimate the Max32 can hold nearly a full minute of voice-quality audio.

Playing with the idea, we found we could do one better. Actually, several better. A limitation of SD card-based players like the Wave Shield is that they can only play one sound at a time. Dealing with the FAT filesystem and buffering audio data off the card takes nearly everything the Arduino’s little ATmega chip can muster…polyphonic sound requires kludges. But our flash-resident audio samples on the chipKIT are trivial to access. With the fast 32-bit CPU, many samples can be processed simultaneously…and then, with gobs of RAM, time-based effects such as reverb can be added. And before we knew it, there was a toy synthesizer sitting on the table:

Having previously dabbled with the PIC32 using Microchip’s tools, we were surprised by the simplicity with which this went together. A few early rough spots aside, the chipKIT and MPIDE environment show major promise for being every bit as simple as Arduino. In fact, the whole build was completed faster than the documentation phase. And then a second surprise, even to us: everything in the parts list, aside from the chipKIT board itself, is common stuff that could be found at RadioShack. No funky special ICs, components or mail-order shields. Most of the “magic” is in software, thanks to this fast microcontroller.

Here’s a demonstration of the finished mini-synth in action:


Please excuse the demonstrator’s tragic lack of rhythm and coordination. This is why professional musicians get paid millions while amateurs lead sad lives as technology bloggers. Be thankful that we spared you the blooper reel.

Input is via five piezoelectric transducers (RadioShack #273-0073, $2.19 each) attached to analog inputs A0 through A4. We could have just used pushbuttons, sure, but we wanted something that could sense the pressure of each hit, and these were cheaper than force-sensitive resistors. Piezo sensors have a specific polarity, and the positive side (red wires) should connect to the analog inputs, and black to ground. There’s also a 2000 Ohm resistor added across each element:

Input for the reverb effect is straightforward. Two 10K potentiometers on analog inputs A6 and A7 (these are on the second row of analog inputs on the chipKIT Uno32, not present on Arduino). One controls the amplitude, the other controls the delay:

Finally, sound output uses high-speed PWM output on digital pin 3, passed through a simple low-pass filter to a headphone jack:

On our breadboard we’re using a handy little headphone breakout board from SparkFun, but one could just solder the appropriate wire leads onto a bare jack from “The Shack” (ugh). You may want to optionally add a 1 Meg pot just before the headphone jack. The circuit worked fine as-is with headphones or an amplified iPod speaker, but totally saturated our camera’s microphone input when fed directly.

This demo uses 16 KHz sound samples. As per Nyquist theory, the low-pass filter is then designed for an 8 KHz (-ish) cutoff frequency. For purely voice applications, half those rates should be sufficient (saving flash space and allowing longer samples), and the two resistor values should then be doubled.

And that’s it for parts. Can you believe it? On to the code…

To begin, we need something that can convert sound files into a format the C compiler can use. An ugly little UNIX command-line utility converts WAV files from a very specific format (8-bit mono, uncompressed) into C header files that can be #included by the MPIDE project. Arduino normally would use the PROGMEM directive to put these tables into the code flash space, but that’s not required here. Surprisingly, the much-loved Audacity program wouldn’t export 8-bit WAVs, but we found it possible to batch convert sounds using iTunes.

const signed char sample_drum[] = {
        0x00,0x00,0x00,0x00,0x02,0x00 };

We’ll spare you the horror of looking at that code or doing the conversion. You can download the complete set of project files here, and then adapt it to your own needs. The remainder of this article deals only with the MPIDE code.

But first, one fix is required: in our prior article, we encountered an issue with the chipKIT’s analog read speed, and a fix was discussed in the comments. This involves scrounging among the MPIDE source files for “wiring_analog.c” and changing a few lines. The old code resembles:

while ( ! mAD1GetIntFlag() ) { }
analogValue = ReadADC10(0);

This should be changed to:

while ( ! mAD1GetIntFlag() ) { }
analogValue = ReadADC10(0);

We’re told this change will be incorporated into later releases of the toolkit and this won’t be necessary for much longer. If you’re just ripping out the digital audio code from this project and ignoring this drum pad stuff, you can skip the change altogether.

And then there’s our sketch code:

// Mini sampling synthesizer for chipKIT Uno32.

#include "sounds.h"       // N_SAMPLES and data are here
#define PWM_PIN         3 // OC1 PWM output - don't change
#define SAMPLE_RATE 16000 // All samples fixed at 16 KHz
#define MAX_SOUNDS     10 // Polyphonic limit
#define MAX_ECHO     4000 // 1/4 sec fits in Uno32 RAM

  echo_data[MAX_ECHO]; // Circular buffer for echo

  echo_delay = 0, // Duration of echo effect
  echo_vol   = 0, // Echo effect volume (0-1023)
  echo_pos   = 0; // Current position in echo buffer
volatile int      // May change during interrupt:
  n_sounds   = 0; // Number of sounds currently playing

struct soundStruct {
  int sample; // Index of corresponding audio sample
  int pos;    // Current position within sample
  int vol;    // Playback volume, 0-1023
} sound[MAX_SOUNDS];

#define N_PADS N_SAMPLES // One pad for each sample

struct padStruct {
  short max;       // Max pressure during press (0-1023)
  short count;     // Timer for filtering out noise
  byte  triggered; // If set, currently reading a press
  short add;       // If >0, begin sound at next interrupt
} pad[N_PADS];

void setup()
  memset(pad, 0, sizeof(pad));    // Clear drum pad data
  memset(echo_data, 0, sizeof(echo_data)); // Clear echo
  pinMode(PWM_PIN, OUTPUT);     // Enable PWM output pin

  // Open Timer2, 1:1 w/256 tick interval (for 8-bit PWM)
  OpenTimer2(T2_ON | T2_PS_1_1,256);

  // Open Timer1 with interrupt for sample mixer (16 KHz)
  ConfigIntTimer1(T1_INT_ON | T1_INT_PRIOR_3);
  OpenTimer1(T1_ON | T1_PS_1_1, F_CPU / SAMPLE_RATE);

  delay(1);  // Slight delay avoids false trigger at start.

// Piezo transducers as input pads are fussy.
// To avoid false positives, a bit of hysteresis is used:
#define PRESS_MIN     20 // Must read at least this force
#define PRESS_COUNT    3 // for this many samples, then...
#define RELEASE_MAX    8 // Must read less than this force
#define RELEASE_COUNT 15 // for this many samples.
// Still imperfect; there are occasional double-triggerings
// and false triggers on adjacent pads.  Could be addressed
// with better mounting and isolation of pads and/or with
// improved input filtering in code or in hardware.

// The loop() function just reads pad and dial inputs; all
// audio work is done in the subsequent interrupt function.

void loop()
  int i, a;

  for(i = 0; i < N_PADS; i++) {  // Sample each pad...
    a = analogRead(i);

    if(pad[i].triggered) {    // Previously pressed?
      if(a <= RELEASE_MAX) {  // Yes, released now?
        if(++pad[i].count >= RELEASE_COUNT) {  // Really?
          // Sounds aren't added to play list here, just
          // flagged; they're added to the mix in the
          // interrupt.  This avoids a race condition
          // where this code may be trying to add a sound
          // while the interrupt is removing one.
          pad[i].add       = pad[i].max;
          pad[i].triggered = 0;
          pad[i].count     = 0;
      } else {  // Still for new max
        if(a > pad[i].max) pad[i].max = a;
        pad[i].count = 0;  // Reset release counter
    } else if(a >= PRESS_MIN) {  // Untriggered; new press?
      if(++pad[i].count >= PRESS_COUNT) {  // Really?
        pad[i].triggered = 1; // Flag to watch for release
        pad[i].count     = 0;
        pad[i].max       = a;
    } else {  // Untriggered and below press threshold
      pad[i].count = 0;  // Clear press counter

  // Echo parameters come from potentiometers on A6 and A7
  echo_vol   = analogRead(6);
  echo_delay = map(analogRead(7), 0, 1023, 0, MAX_ECHO);

// This is the mixing/sample-playing interrupt,
// invoked at 16 KHz to match the audio sample rate.
// With guidance from Mark Sproul's PIC32 port of
// Brett Hagman's Tone library for Arduino.
extern "C"

void __ISR(_TIMER_1_VECTOR,ipl3) playSample(void)
  int i = 0, sum = 0;

  mT1ClearIntFlag();  // Clear interrupt flag

  while(i < n_sounds) {  // For each sound playing...
    // Waveform is cumulative, NOT averaged
    sum += (int)sample[sound[i].sample].data[sound[i].pos] *
    sound[i].pos++;  // Advance counter.  If end hit...
    if(sound[i].pos >= sample[sound[i].sample].size) {
      n_sounds--;  // Decrement number of sounds playing:
      // Move sound at end of list to the slot currently
      // occupied by the vacating sound (unless the same)
      if(i < n_sounds) {
        memcpy(&sound[i], &sound[n_sounds],
        continue;  // Sound moved; dont advance index
  sum /= 1024;

  // Add in echo effect (if enabled) from circular buffer.
  // This takes place before audio level clipping so that
  // any clipping distortion won't be repeated in echo.
  if((echo_delay > 0) && (echo_vol > 0)) {
    sum += echo_data[echo_pos] * (echo_vol + 1) / 1024;
    echo_data[echo_pos] = sum;
    if(++echo_pos >= echo_delay) echo_pos = 0;

  // Clip audio to 8-bit range.  This may cause distortion
  // when multiple sounds or echo exceed the 8-bit range.
  // Invoking the "quick & dirty" alibi again.
  if(sum < -128)     sum = -128;
  else if(sum > 127) sum =  127;

  SetDCOC1PWM(sum + 128);  // Set PWM output value 0-255

  // Check for any new sounds flagged by loop().
  // Done last because sounds finished above will
  // free up polyphonic slots.
  for(i = 0; i < N_PADS; i++) {
    if(pad[i].add) {
      if(n_sounds < MAX_SOUNDS) {
        sound[n_sounds].sample = i;
        sound[n_sounds].pos    = 0;
        sound[n_sounds].vol    = pad[i].add + 1;
      pad[i].add = 0;  // Clear flag even if not added

} // end extern "C"


The setup() function initializes two timers:

  • Timer 2 and Output Compare 1 (hardware features of the PIC32 chip) are used for pulse width modulation (PWM). In conjunction with the filter previously described, this positions the speaker for each audio sample (Google for “PWM DAC” for explanations and examples). The PWM input clock is set to the chip’s full speed of 80 MHz, with an interval of 256 “ticks” (for 8-bit resolution), yielding a PWM waveform at 312,500 Hz. For this sort of DAC filtering it’s recommended that the PWM frequency be at least ten times the sample rate, so this is more than adequate for our needs. This is also why the code bypasses the native Arduino analogWrite() function for PWM, which operates on a much slower clock. Lastly, using Output Compare 1 dictates that we must use digital pin 3 for the audio output; this is one of the five native hardware PWM lines on this chip.
  • Timer 1 operates at our audio sample frequency (16 KHz) and has an interrupt function attached. This function mixes audio samples and changes the PWM duty cycle of Timer2/OC1. The rates on both of these timers are set up once and never need to change, just the one duty cycle is varied.

This section of the code (and one line in the interrupt function) is admittedly not very Arduino-like, directly accessing hardware features in a non-portable manner. A more formal implementation would abstract these details into a library to which the novice programmer could just pass data. But for the sake of a simple, single-file demo, there it is, warts and all. In many ways, this is just a starting point to work from.

The loop() function reads the state of the piezo sensors and marks sounds to be played (received by the interrupt, later). There’s some crude debouncing of the piezo inputs…this really could use some more sophisticated filtering (which the PIC32 could easily handle), but it was skipped for brevity. The code generally detects varying pressure, but there’s still a fair bit of false triggering going on. In this function the reverb controls are also read: just two analogRead() calls, with the second one then mapped to the full length of the reverb buffer.

The interrupt handler is where all the fun stuff happens, and it’s surprisingly simple.

The extern “C” declaration makes the C++ compiler happy with the interrupt function declaration.

The program is designed for up to ten concurrent sounds, the details of which are held in the “sound” structure array (there’s more than enough CPU performance for greater polyphony, but it’s mostly just a matter that the input pads aren’t terribly practical for this). When a pad hit is sensed, a new item is added to this array (up to the maximum). Structure elements indicate which audio sample is used for this sound, the current playback position within the sample, and the volume level.

Audio samples are stored as signed values (rather than unsigned) because this makes them easier to mix (just add together) and easier to adjust gains (just multiply). Every opportunity is taken to use fixed-point math. From the prior fractal demo, we saw what a massive performance difference this can make — sometimes orders of magnitude. Most of our analog readings (returned as 10 bit integers from 0 to 1023) correspond to a gain (relative volume) value of 0.0 to 1.0 (or 0% to 100%). To perform this scaling in fixed-point units, add 1 to the reading, perform the multiplication (one instruction on the PIC32), then divide by 1024 (a simple shift operation, also one instruction). There’s no loss in accuracy vs. converting to floating-point; the source and destination values are going to be quantized anyway.

// Floating-point, slow:
// scale = float 0.0 to 1.0
out = (int)((float)in * scale);

// Fixed-point, crazy fast:
// scale = integer 0 to 1023
out = in * (scale + 1) / 1024;

Along these lines, note that where the audio samples are summed, this division is skipped until the end. This saves some cycles and the result works out the same. Algebraically speaking, (A/X)+(B/X)=(A+B)/X, and so forth. The interim 32-bit sum isn’t likely to overflow.

Fixed-point math happens again when applying reverb. The echo volume, in the integer variable echo_vol (10 bit again, from one of the analog knobs) is in the range 0 to 1023, corresponding to 0% (no reverb) to 100% (echo is as loud as the original sound). Reverb (in echo_data[] array) is a circular buffer — as sounds are played, the contents here (scaled by echo_vol) are first added to the output, then the result is placed back in the same position in the array and the position counter is incremented by one. When the end of the array is reached (or a shorter limit indicated by echo_delay) we “wrap around” back to the beginning.

The final resulting audio value is clipped to an 8-bit range. This may introduce clipping distortion when many loud sounds are used simultaneously. For brevity again, bells and whistles have been omitted, but courageous programmers could opt to add “soft clipping” here to limit such distortion. There’s ample CPU muscle.

The final 8-bit signed value is then transposed into the unsigned range and fed into the OC1 duty cycle for PWM output.

Lastly, the interrupt checks for any sounds that the loop() function flagged as being “hit,” and adds these to the concurrent play list. This flag-and-add behavior, rather than adding items directly in loop(), avoids a potentially nasty race condition whereby loop() could be in the midst of adding a sound just as the interrupt is removing others, throwing off the counter.

And that’s all there is to it. This demo only uses about one fourth of the storage on the Uno32, which itself has one fourth the capacity of the Max32…and we’ve yet to exploit any sort of compression. There could be some fun applications here, maybe adding better Super Mario sounds to toilets or voice prompts to other chipKIT projects (“Your door is ajar”). What other ideas could you see happening here?

20 thoughts on “ChipKIT Sketch: Mini Polyphonic Sampling Synth

  1. A great project and writeup!

    A bit similar is the project by Markus Gritsch, shown here:
    (well, not exactly – but they both make nice sounds :-)

    I’d love to get the MPIDE environment working with the CUI32 ( ) and UBW32 ( ) boards, along side these great ChipKIT boards … I hope the Arduino community accepts Digilent’s additions for the PIC32 family to integrate them directly into the official Arduino IDE, as it would is great to have things working nicely across multiple families of ICs!

    Phil, we’ve actually been using your great host-side bootloader app ( ) with the CUI32 – which has Microchip’s standard HID bootloader on it by default – one way I could imagine of adding support the CUI32/UBW32 boards would be to incorporate your bootloader app into MPIDE, and then if a user selects CUI32/UBW32 from the boards menu it would use this as the proper bootloader tool. What do you think?

    I guess another way to go would be to write a new PIC32-resident bootloader that configures the on-chip USB controller to show up as a CDC device (virtual COMport) compatible with avrdude … Digilent’s use of the FTDI chip on their boards is possibly a bit annoying here, though – as they might be using the FTDI’s DTR pin as an auto-reset (like many AVR Arduinos do – I haven’t checked Digilent’s schematic). But it should be possible in any case, as users can of course just push the program and reset buttons on the CUI32 to put it into bootloader mode if a compatible bootloader is written.


    Maybe I

  2. @Dan: very cool! The next idea that’d been percolating in my mind was some sort of “vintage” emulation…glad to see someone’s already on it.

    I’ve not had time to look into the workings of MPIDE’s “platforms.txt” and “boards.txt” files, but I would be VERY surprised if it were NOT possible to incorporate the CUI32/UBW32 boards into the workflow. Should just be a matter of a different linker script and calling the alternate bootloader app in place of avrdude. If it’s any indication, during setup day at Maker Faire they had their MPIDE installation also working with an entirely different competing architecture, but this needed to be disabled before the show for obvious reasons. If it can handle that, the UBW32 should be a piece of cake. Mmmm, cake…

  3. Interesting! But from what I can see in “platforms.txt” and “boards.txt”, the competing architecture is not so different really ;-) … and from what I can gather online so far, it’s just a matter of compiling MPIDE’s stk500v2 protocol bootloader for the CUI32:
    but this would of course require the use of an FTDI chip or cable wired directly to the PIC32’s RX and TX lines – I would highly prefer using only one bootloader on the CUI32, either the one it already uses (Microchip HID), or possibly an entirely new bootloader based on the chipKIT one, but with the CDC-stack incorporated in order to avoid having to use the FTDI… this would of course use more memory though.

    The other idea of incorporating your “ubw32” bootloader app into MPIDE for linux & Mac would of course need to have the corresponding Windows bootloader app included for that version of MPIDE (I think Microchip has this open sourced, but haven’t looked at their license) … but then maintaining this setup for all 3 platforms would potentially be a headache.

    At the moment I’m leaning towards the stk500v2-compatible bootloader approach, as then minimal changes would be necessary in MPIDE.

  4. so radioshack didnt have 2k resistors , but I got this working sort of , with 2.2k but the pads are allot more flaky then yours look in the video. I am curious how you went about the mounting Phil I used a pcb and double sided tape.

    @dan the guys working on this (who run the forum for it) have offered to give a hand making other pic32 based boards work. You should drop by the forums and post this where they will see it. THere are a few threads on how to do it, what to edit etc.

  5. @addidis: the pads were secured to the base (a piece of mat board) using hot-melt glue. Try tapping with your fingers, or different spots on the pads (center vs edge). If they’re still too fussy, try a slightly smaller resistor. They’re not perfect as input devices and could be improved, but the article was to focus more on the digital audio side.

    1. Piezos have some crazy output signals that should be conditioned for use as drum triggers.I found an excellent write-up at this site in which the author uses some simple hardware (schottky diodes, RC) to nicely condition the piezo input.
      This looks promising. I’ve been going nuts trying to figure out how to do polyphony on the Arduino. This may be the way to go. Thanks a bunch for pioneering this.

  6. Props to Addidis, who posted over on the chipKIT forums with this WAV-to-.h conversion walkthrough for Windows users:

    Using the WAV to .h file converter
    (for Windows users who want an easy way to use this):

    I am sure there are ways that don’t take 4 or 5 hours to install, but for the sake of making this simple I will trade complexity for a long install. (Cygwin is very useful and used in many programming tutorials.)

    Install Cygwin.
    During install it will ask what packages to load. Save yourself the headache later and click “all” at the top and let it install everything. My antivirus had a few strokes installing this but I just forced it through in normal mode. Check up on the install in case yours requires you to OK it as well, or 5 hours turns into 25.

    Again to make this simple copy the “wavs” folder (from the synth sketch download) to C: right in the root directory. If you’re confused about how to use this then you probably are going to have trouble navigating to the right folder so make it easy and put it in the root directory.

    On your desktop there should be a Cygwin icon (it’s green). Open it.

    In the Cygwin window , type “cd c:/wavs” (with no quotes) and press enter. Then type “make all”. It will spit out a sounds.h file into the folder.

    To change the sounds edit the Makefile in a text editor and edit where it lists the wav filenames. It’s pretty easy to figure out once you de-magic the whole concept of a command line utility into something you understand.

    All of the wavs you’re converting need to conform to the notes in convert.c and must be in the same wavs folder as the Makefile and convert.c or bad things happen. Use headphones the first time you play it – my first few were nothing but speaker-blowing squeals.

    Now that you have tested the process of converting WAVs to a .h file, you’re probably installing iTunes and looking for some way to convert other sounds to the 8-bit mono format. All the tutorials I could find for iTunes used previous versions and the options are all moved around. You will probably find the next link helpful at this point:

    This utility doesn’t include a 16 KHz option, but 11025 or 22050 Hz can be used with a corresponding change to SAMPLE_RATE in the sketch. Tradeoffs in sound quality vs. storage requirements will result.

  7. @Phil (and anyone who’s following the CUI32/UBW32 thread here):

    The discussion moved over to the chipKIT forum, and it seems like we’re already at a stage where this is working!

    Rich Testardi (of StickOS fame, ) has managed to create a bootloader that uses the PIC32’s built-in USB (rather than an FTDI-chip like the chipKIT boards) that is 100% compatible with the AVRDUDE stk500v2 protocol that Arduinos use. See

    This will make CUI32 & UBW32 really ambidextrous, with both Arduino (Wiring language) and StickOS BASIC – and it’s even more flexible, actually: Here is a way to upload MPLAB/X hex files as compiled from standard C code using Rich’s new bootloader, – this would work fine with, for example Microchip’s Application Libraries ( – I see they’ve finally posted beta versions of the MAL for OS X and Linux now)… though I suppose most users who wish to write code in MPLAB/X would also have their own PICKIT3.

    For now, anyone wanting to try this out with a CUI32 or UBW32 would need to have their own PICKIT3 (or other) programmer in order to put the bootloader onto their board. But I’ll ask SparkFun to make this bootloader default for shipping on the upcoming new version of the CUI32 (with PIC32MX795) at some point soon!


  8. Hi, I just make my first Amiga Protracker MOD player with a standalone PIC32 (no more hardware than the ChipKit Max32 or Uno32)

    1 file source code here :

    Better for Max32 because I need to add “Pattern streaming” (large static array at this time)

    It work better than ModPlug for some MOD chiptunes, but quality is 44100 stereo 10-bits-only with linear interpolation I use 100% integer algorithm. I add the vibrato and tremolo next time, all other effects are 100% Protracker accurate.


  9. Hi
    I am building this synthesizer for a school project, and I would like to use my own original audio. I am wondering after I have converted an audio file to an 8 bit wav, (using Garageband to record and a program I found online to convert the sound file), how I would insert the audio file into the program. I know how to include other Arduino libraries(the #include command), but how would I make another library? Would I have to write a completely new program. Am I overcomplicating this?
    I would also like to use a female 1/4 jack to run an actual instrument cord through the velocity changer in this synthesizer (maybe play a guitar or an electric piano), which I realize is much more complicated than it appears. Anyone have any suggestions? Is it even possible?
    Thanks, Sacul123

  10. Hello, I’m looking to use the chipKIT Uno32 for a museum project, it needs to playback 30 triggered short sounds a couple seconds each, basically piano type sounds, but polyphonic, ok to be read from an SD card. The sensors can be piezo or whatever suggested that would work best.

    Can this be easily done with chipKIT? or is there a better board?
    Is there anyone out there willing to help, or possibly compensated how to accomplish this? please contact me directly at

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.