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!

Kommentare:

  1. Great stuff! This MIDI parsing library in perl is THE solution for my Arduino chiptune player ;) As of yet, I was only able to convert SID files to MIDI files, but not any further. Since the chiptunes for the Arduino are stored in play text format... Here we go!
    Don't think this will keep me from writing new songs for the band, though!

    AntwortenLöschen
  2. Aeeeh, I just found a "small" problem. This works only with MIDI format 0 files. Before using format 1 you have to convert them.

    And there's another problem which is not fixed yet: The Perl Text::CSV parser has problems with double quotes. You have to change them to single quotes before converting CSV to .h :-(

    Ok, I will keep you in touch with my development if you are promissing to write one song a month :-) !

    AntwortenLöschen