Arduino MIDI Chiptune Synthesizer

18,496

59

21

Introduction: Arduino MIDI Chiptune Synthesizer

About: I'm a software engineer by trade, but have a long-standing love of audio, music gear, and home-made electronics.

Relive the fun of early computer game music with an authentic 8-bit chiptune synthesizer, which you can control over MIDI from the comfort of any modern DAW software.

This simple circuit uses an Arduino to drive an AY-3-8910 programmable sound generator chip (or one of its many clones) to recreate that 1980's sound. Unlike the many designs that need specialised software to edit music, this looks like a standard USB MIDI device. The synthesizer has a clever algorithm which tries to keep the most musically-relevant notes playing; in many cases you can throw un-edited MIDI files straight at it and the tune comes right out. Total cost should be about £20.

Step 1: Things You'll Need

The full parts list for this, as you see in the pictures, is as follows:

  • Sparkfun Pro Micro clone (5V, 16MHz option). I used this one on Amazon.
  • Yamaha YM2149F PSG chip. I got mine from eBay.
  • 2 x 100nF ceramic capacitors
  • 1 each of 75R, 1K and 100K resistors (1/4 watt rating is fine).
  • 4.7nF ceramic disc capacitor
  • 1uF electrolytic capacitor (voltage rating > 5V).
  • 40 pin 0.6" DIP IC socket
  • 2 x 12 way 0.1" headers (this one from CPC)
  • Prototyping board, 3" by 2" approx. I bought a bulk pack of these, again on Amazon.

  • PCB mount phono socket
  • Miniature solid-core wire (like this).

You will also need a soldering iron, solder, wire cutters, pliers, and a wire stripper.

Step 2: Alternative Parts

Alternative programmable sound generator chips

The YM2149 I used is a clone of the original General Instruments AY-3-8910 IC. (The first prototype used an AY-3-8910 I bought from eBay, but it turned out the white noise generator wasn't working. Sad face). You can use either for this project without any changes.

General Instruments also made AY-3-8912 and AY-3-8913 variants, which was the same silicon inside smaller packages, without some extra I/O pins. These pins aren't needed for any audio purposes, and this project doesn't use them. You can use an AY-3-8912 or -8913, just follow the pinouts shown above.

Alternative Arduinos

The "Pro Micro" I used is a copy of Sparkfun's Pro Micro board. If you're not confident with Arduino code it's best to stick with this; if you're happy to adapt the design, you'll need the following specifications

  • ATmega 16u4 or 32u4 device (needed to act as a USB MIDI device; the ATmega 168 or 328 can't do this).
  • 5V operation (the AY-3-8910 runs at 5V), and 16MHz clock speed.
  • At least 13 digital I/O lines.
    • Port pin PB5 must be connected (it's used to generate a 1MHz clock signal). On the Pro Micro this is used as the D9 I/O pin.

The Arduino Leonardo and Micro boards both fit the bill, although I haven't tried them.

Other components

The resistors and capacitors used here aren't particularly special. Any parts of (approximately) the right value should work.

Step 3: Laying Out the Circuit Board

To build the circuit, it's best to start by positioning the sockets, then add the resistors and capacitors. We'll cover wiring these together in the next step.

Using the picture above as a guide, position the 40-pin IC socket, turn the board over and just solder in two opposite corner pins first. If the socket isn't then lying flat against the board, it's easy to fix by resoldering one or other pin. When it's OK, solder the rest.

Position the two 12-pin sockets, then insert the Arduino into them to hold them vertical and steady during soldering. Again, soldering two pins at each end first will allow a check before final soldering.

For the audio output socket, I used a small drill to enlarge the PCB holes, as the mounting tags are rather large.

Step 4: Wiring Up

Once the major components are positioned, they can be wired up on the back of the board, following the circuit above.

The audio output components (R2, R3, C2, C3) and decoupling capacitors (C1, C4) can be connected up with solid-core wire (or off-cuts of component leads). The ground and power connections from the Arduino to the PSG chip (red and black wires, in the picture) can now be made.

The Pro Micro's various outputs are wired up to the AY-3-8910 as follows (see the hookup guide for pin assignments):

Signal   Arduino  AY-3-8910 pin

  DA0      D2        37
  DA1      D3        36
  DA2      D4        35
  DA3      D5        34
  DA4      D6        33
  DA5      D7        32
  DA6      D8        31
  DA7      A0/D18    30
  BC1      D10       29
  BC2      MOSI/D16  28
  BDIR     MISO/D14  27
  RESET#   SCLK/D15  23
  CLOCK    D9        22 (via R1, 75 ohm)

Step 5: Programming Using the Arduino IDE

If you're new to Arduino, I'd strongly recommend trying one of the many tutorials on the basics. Sparkfun's hookup guide gives full details. You can check that the basic programming is working by following the "Blinkies" tutorial. Arduinos can be a little tricky to persuade into 'bootloader' mode (where you can load new sketches), so a bit of practice with a simple example is useful.

Once you're happy, download the chiptunes.ino file attached to this page, and build and upload it. (I've found that using the "Arduino/Genuino Micro" board type is OK for this sketch, if you want to skip installing the Sparkfun board support).

Also, note that if you're on a Mac, the "Port" setting will need to be changed once you've loaded the sketch for the first time. With a 'blank' Arduino (or using the Blinky sketch) it will appear as something like /dev/cu.usbmodemXXXX, as shown in the picture above. When the USB MIDI device is active (as used by the chiptunes.ino sketch) it will be /dev/cu.usbmodemMID1.

Step 6: Testing and Using the Synth

Once the Arduino is programmed, your workstation should automatically recognise it as a USB MIDI device. It will appear with the name 'Arduino Micro' - you should be able to see this on Device Manager in Windows, or the "System Information" app in Mac OS.

On a Mac, you can use the Audio MIDI Setup app to run a basic test. Start the app, then choose Window -> Show MIDI Studio. This will bring up the MIDI Studio window - all your MIDI interfaces will appear in a slightly random arrangement - which hopefully will include the 'Arduino Micro' device. If you click the 'Test Setup' icon in the toolbar, and then click the down arrow (see picture) on the Arduino Micro device, the app will send MIDI notes to the synth. (These aren't particularly tuneful!) The synth should make some random sounds at this point.

You can then add 'Arduino Micro' as an output device to your Digital Audio Workstation's MIDI setup, and start to play!

  • The synth responds on MIDI channels 1 to 4. Each channel has a different sound (well, a different volume envelope).
  • MIDI notes between 24 and 96 (C1-C7) are accepted; notes outside this range are ignored.
  • MIDI channel 10 plays drum sounds. Note numbers between 35 and 50 (see

    https://www.midi.org/specifications-old/item/gm-level-1-sound-set) are accepted.

  • There are three voice channels on the AY-3-8910. The synth firmware tries to play the most recently sent note, while keeping the highest and lowest currently-requested notes still playing. Other notes (usually the middle notes in a chord) are cut off if necessary.


And that's about it. Have fun!

Step 7: Footnotes

About the demo tune

The demo tune - Mozart's famous Queen Of The Night aria - was created reasonably quickly from a MIDI file I found on the Internet (https://www.midiworld.com/mozart.htm). Someone else did all the hard work!

I'm using Presonus Studio One on a Mac, and the MIDI file was imported to four separate tracks. A small amount of editing was needed where the accompaniment notes are higher than the main tune, and to remove some of the more objectionable glitching between notes.

The audio you hear on the clip is straight from the synth, with just a touch of EQ and saturation to give it a bit of an 'arcade machine' low-fi feel.

6 People Made This Project!

Recommendations

  • Block Code Contest

    Block Code Contest
  • Game Design: Student Design Challenge

    Game Design: Student Design Challenge
  • Cold Challenge

    Cold Challenge

21 Comments

1
gowron492
gowron492

Question 1 year ago

All I get is a tick, tick, tick... Any suggestions? Does the demo autostart? How do you play midi files on Windows 10? Thanks.

0
j0nas
j0nas

Answer 14 days ago

Hi Gowron492, I know it was some time ago but did you figure out what was wrong with you project? I'm facing the exact same problem. Thank you

0
chemamateos
chemamateos

1 year ago

I give you a code modified with Serial Monitor, and startup play a little sound to check:
_______________________________________________________________

/*
* Placed in the public domain by the author, Ian Harvey, 2018.
*
* Note there is NO WARRANTY.
*/
#include
#include "MIDIUSB.h"
typedef unsigned short int ushort;
typedef unsigned char note_t;
#define NOTE_OFF 0
typedef unsigned char midictrl_t;
// Pin driver ---------------------------------------------
static const int dbus[8] = { 2,3,4,5,6,7,8,A0 };
static const ushort
BC1 = 10,
BC2 = 16,
BDIR = 14,
nRESET = 15,
clkOUT = 9;
static const ushort
DIVISOR = 7; // Set for 1MHz clock
static void clockSetup()
{
// Timer 1 setup for Mega32U4 devices
//
// Use CTC mode: WGM13..0 = 0100
// Toggle OC1A on Compare Match: COM1A1..0 = 01
// Use ClkIO with no prescaling: CS12..0 = 001
// Interrupts off: TIMSK0 = 0
// OCR0A = interval value
TCCR1A = (1 << COM1A0);
TCCR1B = (1 << WGM12) | (1 << CS10);
TCCR1C = 0;
TIMSK1 = 0;
OCR1AH = 0;
OCR1AL = DIVISOR; // NB write high byte first
}
static void setData(unsigned char db)
{
unsigned char bit=1;
for (ushort i=0; i<8; i++)
{
digitalWrite(dbus[i], (db & bit) ? HIGH : LOW);
bit <<= 1;
}
}
static void writeReg(unsigned char reg, unsigned char db)
{
// Enter with BDIR:BC2:BC1 = 000
setData(reg);
digitalWrite(BDIR, HIGH); // -> Latch Address
// TODO check cycle time
digitalWrite(BDIR, LOW); // -> Inactive
setData(db);
digitalWrite(BC2, HIGH); // -> still inactive
digitalWrite(BDIR, HIGH); // -> write
digitalWrite(BDIR, LOW); // -> inactive
digitalWrite(BC2, LOW); // -> starting state
}
// AY-3-8910 driver ---------------------------------------
class PSGRegs
{
public:
enum
{
TONEALOW=0,
TONEAHIGH,
TONEBLOW,
TONEBHIGH,
TONECLOW,
TONECHIGH,
NOISEGEN,
MIXER,
TONEAAMPL,
TONEBAMPL,
TONECAMPL,
ENVLOW,
ENVHIGH,
ENVSHAPE,
IOA,
IOB
};
unsigned char regs[16];
unsigned char lastregs[16];
void init()
{
for (int i=0; i<16; i++)
{
regs[i] = lastregs[i] = 0xFF;
writeReg(i, regs[i]);
}
}
void update()
{
for (int i=0; i<16; i++)
{
if ( regs[i] != lastregs[i] )
{
writeReg(i, regs[i]);
lastregs[i] = regs[i];
}
}
}
void setTone(ushort ch, ushort divisor, ushort ampl)
{
regs[TONEALOW + (ch<<1)] = (divisor & 0xFF);
regs[TONEAHIGH + (ch<<1)] = (divisor >> 8);
regs[TONEAAMPL + ch] = ampl;
ushort mask = (8+1) << ch;
regs[MIXER] = (regs[MIXER] | mask) ^ (1 << ch);
}
void setToneAndNoise(ushort ch, ushort divisor, ushort noisefreq, ushort ampl)
{
regs[TONEALOW + (ch<<1)] = (divisor & 0xFF);
regs[TONEAHIGH + (ch<<1)] = (divisor >> 8);
regs[NOISEGEN] = noisefreq;
regs[TONEAAMPL + ch] = ampl;
ushort mask = (8+1) << ch;
ushort bits = (noisefreq < 16 ? 8 : 0) + (divisor > 0 ? 1 : 0);
regs[MIXER] = (regs[MIXER] | mask) ^ (bits << ch);
}
void setEnvelope(ushort divisor, ushort shape)
{
regs[ENVLOW] = (divisor & 0xFF);
regs[ENVHIGH] = (divisor >> 8);
regs[ENVSHAPE] = shape;
}
void setOff(ushort ch)
{
ushort mask = (8+1) << ch;
regs[MIXER] = (regs[MIXER] | mask);
regs[TONEAAMPL + ch] = 0;
if ( regs[ENVSHAPE] != 0 )
{
regs[ENVSHAPE] = 0;
update(); // Force flush
}
}
};
static PSGRegs psg;
// Voice generation ---------------------------------------
static const ushort
MIDI_MIN=24,
MIDI_MAX=96,
N_NOTES = (MIDI_MAX+1-MIDI_MIN);
static const ushort note_table[N_NOTES] = {
1911, // MIDI 24, 32.70 Hz
1804, // MIDI 25, 34.65 Hz
1703, // MIDI 26, 36.71 Hz
1607, // MIDI 27, 38.89 Hz
1517, // MIDI 28, 41.20 Hz
1432, // MIDI 29, 43.65 Hz
1351, // MIDI 30, 46.25 Hz
1276, // MIDI 31, 49.00 Hz
1204, // MIDI 32, 51.91 Hz
1136, // MIDI 33, 55.00 Hz
1073, // MIDI 34, 58.27 Hz
1012, // MIDI 35, 61.74 Hz
956, // MIDI 36, 65.41 Hz
902, // MIDI 37, 69.30 Hz
851, // MIDI 38, 73.42 Hz
804, // MIDI 39, 77.78 Hz
758, // MIDI 40, 82.41 Hz
716, // MIDI 41, 87.31 Hz
676, // MIDI 42, 92.50 Hz
638, // MIDI 43, 98.00 Hz
602, // MIDI 44, 103.83 Hz
568, // MIDI 45, 110.00 Hz
536, // MIDI 46, 116.54 Hz
506, // MIDI 47, 123.47 Hz
478, // MIDI 48, 130.81 Hz
451, // MIDI 49, 138.59 Hz
426, // MIDI 50, 146.83 Hz
402, // MIDI 51, 155.56 Hz
379, // MIDI 52, 164.81 Hz
358, // MIDI 53, 174.61 Hz
338, // MIDI 54, 185.00 Hz
319, // MIDI 55, 196.00 Hz
301, // MIDI 56, 207.65 Hz
284, // MIDI 57, 220.00 Hz
268, // MIDI 58, 233.08 Hz
253, // MIDI 59, 246.94 Hz
239, // MIDI 60, 261.63 Hz
225, // MIDI 61, 277.18 Hz
213, // MIDI 62, 293.66 Hz
201, // MIDI 63, 311.13 Hz
190, // MIDI 64, 329.63 Hz
179, // MIDI 65, 349.23 Hz
169, // MIDI 66, 369.99 Hz
159, // MIDI 67, 392.00 Hz
150, // MIDI 68, 415.30 Hz
142, // MIDI 69, 440.00 Hz
134, // MIDI 70, 466.16 Hz
127, // MIDI 71, 493.88 Hz
119, // MIDI 72, 523.25 Hz
113, // MIDI 73, 554.37 Hz
106, // MIDI 74, 587.33 Hz
100, // MIDI 75, 622.25 Hz
95, // MIDI 76, 659.26 Hz
89, // MIDI 77, 698.46 Hz
84, // MIDI 78, 739.99 Hz
80, // MIDI 79, 783.99 Hz
75, // MIDI 80, 830.61 Hz
71, // MIDI 81, 880.00 Hz
67, // MIDI 82, 932.33 Hz
63, // MIDI 83, 987.77 Hz
60, // MIDI 84, 1046.50 Hz
56, // MIDI 85, 1108.73 Hz
53, // MIDI 86, 1174.66 Hz
50, // MIDI 87, 1244.51 Hz
47, // MIDI 88, 1318.51 Hz
45, // MIDI 89, 1396.91 Hz
42, // MIDI 90, 1479.98 Hz
40, // MIDI 91, 1567.98 Hz
38, // MIDI 92, 1661.22 Hz
36, // MIDI 93, 1760.00 Hz
34, // MIDI 94, 1864.66 Hz
32, // MIDI 95, 1975.53 Hz
30, // MIDI 96, 2093.00 Hz
};
struct FXParams
{
ushort noisefreq;
ushort tonefreq;
ushort envdecay;
ushort freqdecay;
ushort timer;
};
struct ToneParams
{
ushort decay;
ushort sustain; // Values 0..32
ushort release;
};
static const ushort MAX_TONES = 4;
static const ToneParams tones[MAX_TONES] = {
{ 30, 24, 10 },
{ 30, 12, 8 },
{ 5, 8, 7 },
{ 10, 31, 30 }
};
class Voice
{
public:
ushort m_chan; // Index to psg channel
ushort m_pitch;
int m_ampl, m_decay, m_sustain, m_release;
static const int AMPL_MAX = 1023;
ushort m_adsr;
void init (ushort chan)
{
m_chan = chan;
m_ampl = m_sustain = 0;
kill();
}
void start(note_t note, midictrl_t vel, midictrl_t chan)
{
const ToneParams *tp = &tones[chan % MAX_TONES];
m_pitch = note_table[note - MIDI_MIN];
if ( vel > 127 )
m_ampl = AMPL_MAX;
else
m_ampl = 768 + (vel << 1);
m_decay = tp->decay;
m_sustain = (m_ampl * tp->sustain) >> 5;
m_release = tp->release;
m_adsr = 'D';
psg.setTone(m_chan, m_pitch, m_ampl >> 6);
}
struct FXParams m_fxp;
void startFX( const struct FXParams &fxp )
{
m_fxp = fxp;
if (m_ampl > 0)
{
psg.setOff(m_chan);
}
m_ampl = AMPL_MAX;
m_adsr = 'X';
m_decay = fxp.timer;
psg.setEnvelope(fxp.envdecay, 9);
psg.setToneAndNoise(m_chan, fxp.tonefreq, fxp.noisefreq, 31);
}
void stop()
{
if ( m_adsr == 'X' )
return; // Will finish when ready...
if ( m_ampl > 0 )
{
m_adsr = 'R';
}
else
psg.setOff(m_chan);
}
void update100Hz( )
{
if ( m_ampl == 0 )
return;
switch( m_adsr )
{
case 'D':
m_ampl -= m_decay;
if ( m_ampl <= m_sustain )
{
m_adsr = 'S';
m_ampl = m_sustain;
}
break;
case 'S':
break;
case 'R':
if ( m_ampl < m_release )
m_ampl = 0;
else
m_ampl -= m_release;
break;
case 'X':
// FX is playing.
if ( m_fxp.freqdecay > 0 )
{
m_fxp.tonefreq += m_fxp.freqdecay;
psg.setToneAndNoise(m_chan, m_fxp.tonefreq, m_fxp.noisefreq, 31);
}
m_ampl -= m_decay;
if ( m_ampl <= 0 )
{
psg.setOff(m_chan);
m_ampl = 0;
}
return;
default:
break;
}
if ( m_ampl > 0 )
psg.setTone(m_chan, m_pitch, m_ampl >> 6);
else
psg.setOff(m_chan);
}
bool isPlaying()
{
return (m_ampl > 0);
}
void kill()
{
psg.setOff(m_chan);
m_ampl = 0;
}
};
const ushort MAX_VOICES = 3;
static Voice voices[MAX_VOICES];
// MIDI synthesiser ---------------------------------------
// Deals with assigning note on/note off to voices
static const uint8_t PERC_CHANNEL = 10;
static const note_t
PERC_MIN = 35,
PERC_MAX = 50;
static const struct FXParams perc_params[PERC_MAX-PERC_MIN+1] =
{
// Mappings are from the General MIDI spec at https://www.midi.org/specifications-old/item/gm-level-1-sound-set
// Params are: noisefreq, tonefreq, envdecay, freqdecay, timer
{ 9, 900, 800, 40, 50 }, // 35 Acoustic bass drum
{ 8, 1000, 700, 40, 50 }, // 36 (C) Bass Drum 1
{ 4, 0, 300, 0, 80 }, // 37 Side Stick
{ 6, 0, 1200, 0, 30 }, // 38 Acoustic snare
{ 5, 0, 1500, 0, 90 }, // 39 (D#) Hand clap
{ 6, 400, 1200, 11, 30 }, // 40 Electric snare
{ 16, 700, 800, 20, 30 }, // 41 Low floor tom
{ 0, 0, 300, 0, 80 }, // 42 Closed Hi Hat
{ 16, 400, 800, 13, 30 }, // 43 (G) High Floor Tom
{ 0, 0, 600, 0, 50 }, // 44 Pedal Hi-Hat
{ 16, 800, 1400, 30, 25 }, // 45 Low Tom
{ 0, 0, 800, 0, 40 }, // 46 Open Hi-Hat
{ 16, 600, 1400, 20, 25 }, // 47 (B) Low-Mid Tom
{ 16, 450, 1500, 15, 22 }, // 48 Hi-Mid Tom
{ 1, 0, 1800, 0, 25 }, // 49 Crash Cymbal 1
{ 16, 300, 1500, 10, 22 }, // 50 High Tom
};
static const int REQ_MAP_SIZE = (N_NOTES+7) / 8;
static uint8_t m_requestMap[REQ_MAP_SIZE];
// Bit is set for each note being requested
static midictrl_t m_velocity[N_NOTES];
// Requested velocity for each note
static midictrl_t m_chan[N_NOTES];
// Requested MIDI channel for each note
static uint8_t m_highest, m_lowest;
// Highest and lowest requested notes
static const uint8_t NO_NOTE = 0xFF;
static const uint8_t PERC_NOTE = 0xFE;
static uint8_t m_playing[MAX_VOICES];
// Which note each voice is playing
static const uint8_t NO_VOICE = 0xFF;
static uint8_t m_voiceNo[N_NOTES];
// Which voice is playing each note
static bool startNote( ushort idx )
{
for (ushort i=0; i {
if ( m_playing[i]==NO_NOTE )
{
voices[i].start( MIDI_MIN + idx, m_velocity[idx], m_chan[idx] );
m_playing[i] = idx;
m_voiceNo[idx] = i;
return true;
}
}
return false;
}
static bool startPercussion( note_t note )
{
ushort i;
for (i=0; i {
if ( m_playing[i] == NO_NOTE || m_playing[i] == PERC_NOTE )
{
if ( note >= PERC_MIN && note <= PERC_MAX )
{
voices[i].startFX(perc_params[note-PERC_MIN]);
m_playing[i] = PERC_NOTE;
}
return true;
}
}
return false;
}
static bool stopNote( ushort idx )
{
uint8_t v = m_voiceNo[idx];
if ( v != NO_VOICE )
{
voices[v].stop();
m_playing[v] = NO_NOTE;
m_voiceNo[idx] = NO_VOICE;
return true;
}
return false;
}
static void stopOneNote()
{
uint8_t v, chosen = NO_NOTE;
// At this point we have run out of voices.
// Pick a voice and stop it. We leave a voice alone
// if it's playing the highest requested note. If it's
// playing the lowest requested note we look for a 'better'
// note, but stop it if none found.
for (v=0; v {
uint8_t idx = m_playing[v];
if (idx == NO_NOTE) // Uh? Perhaps called by mistake.
return;
if (idx == m_highest)
continue;
if (idx == PERC_NOTE)
continue;
chosen = idx;
if (idx != m_lowest)
break;
// else keep going, we may find a better one
}
if (chosen != NO_NOTE)
{
stopNote(chosen);
}
}
static void updateRequestedNotes()
{
m_highest = m_lowest = NO_NOTE;
ushort i,j;
// Check highest requested note is playing
// Return true if note was restarted; false if already playing
for (i=0; i < REQ_MAP_SIZE; i++ )
{
uint8_t req = m_requestMap[i];
if ( req == 0 )
continue;
for ( j=0; j < 8; j++ )
{
if ( req & (1 << j) )
{
ushort idx = i*8 + j;
if ( m_lowest==NO_NOTE || m_lowest > idx )
m_lowest = idx;
if ( m_highest==NO_NOTE || m_highest < idx )
m_highest = idx;
}
}
}
}
static bool restartANote()
{
if ( m_highest != NO_NOTE && m_voiceNo[m_highest] == NO_VOICE )
return startNote(m_highest);
if ( m_lowest != NO_NOTE && m_voiceNo[m_lowest] == NO_VOICE )
return startNote(m_lowest);
return false;
}
static void synth_init ()
{
ushort i;
for (i=0; i m_requestMap[i] = 0;
for (i=0; i {
m_velocity[i] = 0;
m_voiceNo[i] = NO_VOICE;
}
for (i=0; i {
m_playing[i] = NO_NOTE;
}
m_highest = m_lowest = NO_NOTE;
}
static void noteOff( midictrl_t chan, note_t note, midictrl_t vel )
{
if ( chan == PERC_CHANNEL || note < MIDI_MIN || note > MIDI_MAX )
return; // Just ignore it
ushort idx = note - MIDI_MIN;
m_requestMap[idx/8] &= ~(1 << (idx & 7));
m_velocity[idx] = 0;
updateRequestedNotes();
if ( stopNote(idx) )
{
restartANote();
}
}
static void noteOn( midictrl_t chan, note_t note, midictrl_t vel )
{
if ( vel == 0 )
{
noteOff(chan, note, 0);
return;
}
if ( chan == PERC_CHANNEL )
{
if ( !startPercussion(note) )
{
stopOneNote();
startPercussion(note);
}
return;
}
// Regular note processing now
if ( note < MIDI_MIN || note > MIDI_MAX )
return; // Just ignore it
ushort idx = note - MIDI_MIN;
if ( m_voiceNo[idx] != NO_VOICE )
return; // Already playing. Ignore this request.
m_requestMap[idx/8] |= 1 << (idx & 7);
m_velocity[idx] = vel;
m_chan[idx] = chan;
updateRequestedNotes();
if ( !startNote(idx) )
{
stopOneNote();
startNote(idx);
}
}
static void update100Hz()
{
for (ushort i=0; i {
voices[i].update100Hz();
if ( m_playing[i] == PERC_NOTE && ! (voices[i].isPlaying()) )
{
m_playing[i] = NO_NOTE;
restartANote();
}
}
}
// Main code ----------------------------------------------
static unsigned long lastUpdate = 0;
static unsigned long lastUpdat = 0;
int period = 200;
unsigned long time_now = 0;
void setup() {
Serial.begin(9600);
// Hold in reset while we set up the reset
pinMode(nRESET, OUTPUT);
digitalWrite(nRESET, LOW);
pinMode(clkOUT, OUTPUT);
digitalWrite(clkOUT, LOW);
clockSetup();
pinMode(BC1, OUTPUT);
digitalWrite(BC1, LOW);
pinMode(BC2, OUTPUT);
digitalWrite(BC2, LOW);
pinMode(BDIR, OUTPUT);
digitalWrite(BDIR, LOW);
for (ushort i=0; i<8; i++)
{
pinMode(dbus[i], OUTPUT);
digitalWrite(dbus[i], LOW);
}
delay(100);
digitalWrite(nRESET, HIGH);
delay(10);
lastUpdate = millis();
psg.init();
for (ushort i=0; i {
voices[i].init(i);
}
synth_init();
delay(300);
midiEventPacket_t rx = MidiUSB.read();
noteOn( 144 & 0xF, 62, 60 );
time_now = millis();
while(millis() < time_now + period){
}
psg.update();
noteOn( 144 & 0xF, 62, 0 );
time_now = millis();
while(millis() < time_now + period){
}
psg.update();
noteOn( 144 & 0xF, 62, 60 );
time_now = millis();
while(millis() < time_now + period){
}
psg.update();
noteOn( 144 & 0xF, 62, 0 );
psg.update();
noteOn( 144 & 0xF, 79, 53 );
time_now = millis();
while(millis() < time_now + period){
}
psg.update();
noteOn( 144 & 0xF, 52, 53 );
time_now = millis();
while(millis() < time_now + period){
}
psg.update();
noteOn( 144 & 0xF, 70, 53 );
time_now = millis();
while(millis() < time_now + period){
}
psg.update();
noteOn( 144 & 0xF, 70, 0 );
time_now = millis();
while(millis() < time_now + period){
}
psg.update();
noteOn( 144 & 0xF, 79, 0 );
time_now = millis();
while(millis() < time_now + period){
}
psg.update();
noteOn( 144 & 0xF, 52, 0 );
time_now = millis();
while(millis() < time_now + period){
}
psg.update();
noteOn( 144 & 0xF, 79, 0 );
time_now = millis();
while(millis() < time_now + period){
}
psg.update();
}
unsigned long nows = 0;
unsigned long nows1 = 0;
void loop() {
midiEventPacket_t rx = MidiUSB.read();
if ( rx.header==0x9 ) // Note on
{
noteOn( rx.byte1 & 0xF, rx.byte2, rx.byte3 );
nows = millis();
if ( nows > (nows1 + 2) )
{
Serial.println("-----------");
nows1 = millis();
}
Serial.print(rx.byte1);Serial.print(" ");
Serial.print(rx.byte2);Serial.print(" ");
Serial.println(rx.byte3);
}
else if ( rx.header==0x8 ) // Note off
{
noteOff( rx.byte1 & 0xF, rx.byte2, rx.byte3 );
Serial.print(rx.byte1);Serial.print(" ");
Serial.print(rx.byte2);Serial.print(" ");
Serial.println(rx.byte3);
Serial.println("...................");
}
unsigned long now = millis();
if ( (now - lastUpdate) > 10 )
{
update100Hz();
lastUpdate += 10;
}
psg.update();
}


0
davidadkins1
davidadkins1

Reply 8 months ago

There are syntax errors in this test. For instance (pun intended):
static bool startNote( ushort idx )
{
for (ushort i=0; i // Here is a syntax error
{
if ( m_playing[i]==NO_NOTE )
{
voices[i].start( MIDI_MIN + idx, m_velocity[idx], m_chan[idx] );
m_playing[i] = idx;
m_voiceNo[idx] = i;
return true;
}

0
davidadkins1
davidadkins1

Tip 8 months ago on Step 5

Like me, you may have to use the Arduino library manager to add "MIDIUSB."
Also be careful to specify a (5V, 16 MHz) processor in the Arduino tools menu..

0
moruscerberus
moruscerberus

Question 1 year ago on Introduction

Is it designed in Fritzing, if so is it possible to get the original file?

0
natrinicle
natrinicle

1 year ago

Thanks for the write-up! I followed your guide and built mine though in a slightly less professional looking way (dead-bug style).

MVIMG_20210929_091622.jpgMVIMG_20210929_091630.jpgMVIMG_20210929_091940.jpg
0
fettle
fettle

1 year ago

I'm building this with an AY-3-8913 which does have a BC2 pin. Does that make any difference to how I wire this up?

0
AlexanderM161
AlexanderM161

1 year ago

is it possible that this is not perfectly in tune with the midi notes ? i get lower notes sometimes.. so not possible to play a full 12 key scale ? or maybe my ay-38912 is damaged ?

1
marathonmusicgym
marathonmusicgym

1 year ago

For anyone hoping to have simultaneous 5-pin MIDI Input and USB-over-MIDI, I finally got it working! Hope this helps:

1. Set up your optocoupler circuit -- https://www.notesandvolts.com/2015/02/midi-and-ard...
2. Install MIDI libraries. Add "#include <MIDI.h>" to your preamble.
3. Add "MIDI.read();" in your loop() section.
4. Add the following to your setup() section:

MIDI.setHandleNoteOn(noteOn);
MIDI.setHandleNoteOff(noteOff);
MIDI.begin(MIDI_CHANNEL_OMNI);

5. Enjoy!

The only thing I'm noticing is that the 5-pin MIDI Input is translating input to the wrong channel -- it is seeing the percussion channel (10) on channel 9. Not quite sure why it would do this offhand, but I'm a noob. Any ideas?



0
brother_lui
brother_lui

Reply 1 year ago

you should change the channel in the code. search for:
static const uint8_t PERC_CHANNEL = 9;

and change it to 10, or whatever you want.

2
McTest42
McTest42

Question 3 years ago on Step 1

Hi awesome project. Would it be possible to have a real midi in connector that is going to the rx input of the arduino?

1
AlexanderM161
AlexanderM161

Answer 2 years ago

thats easy. did this already, just convert the usbMidi rx to the serial midi

1
rreemmoorrii
rreemmoorrii

Question 2 years ago

if atmega328 cant do usb , how about native din 5?

0
whc2001
whc2001

Answer 2 years ago

I think it's possible, the source code uses MIDIUSB.h and constantly read for MIDI messages coming from the USB MIDI device and trigger noteOn() and noteOff() respectively. I think you can just replace that with a SoftSerial at 31250bps (the baudrate the traditional MIDI interface uses) and wire a 5 pin DIN port, since MIDI interface is in fact just opto-isolated UART. For this simple usage I don't think isolation is even needed.

1
Janterra
Janterra

Question 3 years ago

Decided to give this a try and have all parts but the audio jacks
ordered. I'm wanting to have the option of outputting all three analog
channels to separate output jacks and was wondering if it's as simple as
duplicating the output circuit for each output. is that the best way to
go about it?

0
akirapaw
akirapaw

Question 3 years ago on Step 4

I have purchased all the components and am preparing to build this (waiting for my chips to arrive from China). What technique did you use to make the wire connections? Did you loop the wire ends around the pins and leads and solder both together? Or did you solder all pins and leads to the board and then resolder the wires on top of them? I haven't used pcb prototyping boards very much (usually just pcb kits), so I'm unsure what the optimal procedure is here. Thanks for any help!

0
therawski
therawski

Answer 3 years ago

Solder the larger components first like the IC socket then on the other side solder everything together with wires or the rest of the component leads, yeah basically resolder

0
dfdtitmouse
dfdtitmouse

3 years ago

Good to hear, I'm building a new one (to test 10 Chinese clones to make sure they work, or just pure...) - will be using the Leonardo & a 40 pin ZIF socket (with other GLU logic/circuits for data lights, etc.).

1
therawski
therawski

Question 3 years ago on Step 7

I built this but I get a very low output with a lot of high pitched noise. Not sure what I did wrong

EDIT: Just want to thank everyone that got back to me on this, the advice helped me learn what was actually happening. In my ignorance, I didn't solder the capacitor to all of the ground pins are the Arduino, doing so fixed my problem. Now I know that we are using them to filter that noise.