Dienstag, 13. Dezember 2011

Another funny thing for the Fluxamasynth shield

Here is another simple program which plays GM-Files (GM: General MIDI) with a Fluxamasynth. You cannot use the original MIDI file because the Arduino has not so much memory for bigger files and it is quit easier for the playing code if the data is minimized for only neccesary events.

So I decided to decode the MIDI file with some perl utils (http://www.fourmilab.ch/webtools/midicsv/) to a CSV file. Than the CSV-MIDI-file is parsed by my own Perl program which drops lines which the Fluxamasynth can't handle or should not use. It also stops converting after 22487 bytes of data. This seems to be a size which Arduino can handle without any errors.
  • Notice that this converter only works with midi files format 0 (convert format 1 first)!
  • Notice also that you have to replace every double quote with a single quote before converting CVS to Arduino-Header format!
Take a look at the csv2fluxama-c.pl converter:
#!/usr/bin/perl

use Text::CSV;
use Data::Dumper;

my @data;
my $csv = Text::CSV->new({binary => 1, auto_diag => 1})
          || die "Cannot use CSV: ".Text::CSV->error_diag ();

open(SMF,"<$ARGV[0]") || die("Cannot open $ARGV[0]\n");
while(my $d=$csv->getline(SMF))
{
        push @data,$d;
}
$csv->eof || $csv->error_diag();
close(SMF);

print "FLASH_ARRAY(byte, midi_data,\n";

my $old_time=0;
my $out="";
my $max=22487;

while($d=shift(@data))
{
        if($max<5)
        {
                last;
        }

        if($$d[2] eq " Tempo")
        {
                $tempo=int($$d[3]);
                next;
        }
        elsif($$d[2] eq " System_exclusive")
        {
                next;
        }
        elsif($$d[2] eq " Pitch_bend_c")
        {
                $out=" 3,".$$d[3].",".(($$d[4]&65280)>>8).",".($$d[4]&255);
                $max-=5;
        }
        elsif($$d[2] eq " Control_c" && $$d[4] == 7)
        {
                $out=" 2,".$$d[3].",".$$d[5];
                $max-=4;
        }
        elsif($$d[2] eq " Control_c" && $$d[4] == 10) # Pan
        {
                $out=" 5,".$$d[3].",".$$d[5];
                $max-=4;
        }
        elsif($$d[2] eq " Control_c" && $$d[4] == 64) # Sustain
        {
                $out=" 6,".$$d[3].",".$$d[5];
                $max-=4;
        }
        elsif($$d[2] eq " Program_c")
        {
                $out=" 4,".$$d[3].",".$$d[4];
                $max-=4;
        }
        elsif($$d[2] eq " Time_signature")
        {
                $ticks_per_beat=$$d[5];
                next;
        }
        elsif($$d[2] eq " Note_on_c")
        {
                $out=" 0,".$$d[3].",".$$d[4].",".$$d[5];
                $max-=5;
        }
        elsif($$d[2] eq " Note_off_c")
        {
                $out=" 1,".$$d[3].",".$$d[4];
                $max-=4;
        }
        else
        {
                next;
        }

        # delay value
        print "   ".($$d[1]-$old_time).",".$out;
        $old_time=$$d[1];

        print ", // ".$$d[0].",".$$d[1].",".$$d[2].",".$$d[3].",".$$d[4].",".$$d[5],"\n";
}

print ");\n";
print "const unsigned long tempo=".$tempo.";\n";
print "const unsigned int ticks_per_beat=".$ticks_per_beat.";\n";
 
The output is written to stdout in Flash library format. This file should be included into the following sketch:

#include <Fluxamasynth_NSS.h>
#include <NewSoftSerial.h>
#include <Flash.h>        // needed for storing the song data in PROG_MEM
#include <FlexiTimer2.h>  // for correct, interrupt based timing
#include "converted-midi-file.h"

#define MASTER_VOL_MAX 75    // the maximum volume
#define TEMPO 5

Fluxamasynth synth;
int sp=0;  // the song pointer
const int events=midi_data.count();

void event()
{
  if(sp<events)
  {
    // check the type of event
    switch(midi_data[sp+1])
    {
    case 0:  // NoteOn
      synth.noteOn(midi_data[sp+2],midi_data[sp+3],midi_data[sp+4]);
      sp+=5;
      break;
    case 1:  // NoteOff
      synth.noteOff(midi_data[sp+2],midi_data[sp+3]);
      sp+=4;
      break;
    case 2:  // ChannelVolume
      synth.setChannelVolume(midi_data[sp+2], midi_data[sp+3]);
      sp+=4;
      break;
    case 3:  // PitchBend
      synth.pitchBend(midi_data[sp+2], (midi_data[sp+3]<<8)+midi_data[sp+4]);
      sp+=5;
      break;
    case 4:  // ProgramChange
      synth.programChange(0,midi_data[sp+2], midi_data[sp+3]);
      sp+=4;
      break;
    /* case 5:  // Pan
      synth.Panorama(0,midi_data[sp+2], midi_data[sp+3]);
      sp+=4;
      break;
    case 6:  // Sustain
      synth.Sustain(0,midi_data[sp+2], midi_data[sp+3]);
      sp+=4;
      break; */
    }

    // timer for the next event
    FlexiTimer2::set(midi_data[sp]*ticks_per_beat/TEMPO,1000.0/tempo,event);
    FlexiTimer2::start();
  }
  else
  {
    // after the end of the song reset the Fluxamasynth
    synth.midiReset();
    synth.setMasterVolume(MASTER_VOL_MAX);
    FlexiTimer2::stop();
    sp=0;
    FlexiTimer2::set(5000*ticks_per_beat/TEMPO,1000.0/tempo,event);
    FlexiTimer2::start();
  }
}

void setup()
{
  synth.midiReset();
  synth.setMasterVolume(MASTER_VOL_MAX);
  FlexiTimer2::set(midi_data[sp]*ticks_per_beat/TEMPO,1000.0/tempo,event);
  FlexiTimer2::start();
}

void loop()
{
  ; // Windows like... ;-)
}
You need the following libraries: ... and finally everything should work like this:
Have fun - and don't forget: Pointless resistance!

Now I have my Fluxamasynth shield!

I just wrote a very simple program which uses NewSoftSerial for the MIDI communication towards the Fluxamasynth shield on pin 4. This small program runs through the 256 sounds of the shield (on MIDI channel 1) and plays a chord (randomized start note).



The source code should be easy to understand:

#include <fluxamasynth_nss.h>
#include <newsoftserial.h>

Fluxamasynth synth;

#define c3 48          // define our notes to their midi values
#define e3 52
#define g3 55
#define c4 60

void setup()
{
  Serial.begin(9600);
  synth.setMasterVolume(100);
}

void loop()
{
  byte i=0,n=0,z=0;

  for (n=0;n<=1;n++)
  {
    for(i=0;i<=127;i++)
    {
      synth.programChange(n*127,0, i);

      Serial.print("Bank: ");
      Serial.print(n,DEC);
      Serial.print(" Sound: ");
      Serial.println(i,DEC);

      z=random(0,12);

      synth.noteOn(0, c3+z, 107);
      delay(100);
      synth.noteOn(0, e3+z, 107);
      delay(100);
      synth.noteOn(0, g3+z, 107);
      delay(100);
      synth.noteOn(0, c4+z, 107);

      delay(200);
      synth.noteOff(0, c3+z);
      synth.noteOff(0, e3+z);
      synth.noteOff(0, g3+z);
      synth.noteOff(0, c4+z);
      delay(200);
    }
  }
}

You also need the NewSoftSerial library from http://arduiniana.org/libraries/newsoftserial/ and the Fluxamasynth library from http://wiki.moderndevice.com/pmwiki.php?n=MD.Fluxamasynth.

The next step is to try how to play MIDI songs. The Fluxmasynth shield can play up to 8 voices and a drumset!