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!

Mittwoch, 23. November 2011

Arduino MIDI Arpeggiator?

Has anyone code for a MIDI-Arpeggiator which can be used on an Arduino? I know that ther is a guy who built his own Arpeggiator - really nice piece of hardware. But I only need an arpeggiator class. The UI maybe a LCD and some buttons...

I think I will write one on my own. But currently there are other goals.

Arduino - Music - MIDI - Fluxamasynth: My current project

I was just ordering a shield which seems to have the same power as my old Roland D-110: Fluxamasynth (see http://wiki.moderndevice.com/pmwiki.php?n=MD.Fluxamasynth). There is a library for the shield which plays sound via MIDI (pin 1/2) and there is a port of the library which uses NewSoftSerial so you can use other pins than 1/2 (and you can use 1/2 for debugging output).

I also adopted the MIDI library to use NewSoftSerial as backend (instead of Hardware Serial) but until now I had no time to test this. I hope I can do this when I get my Fluxamasynth shield.

Actually I have written a simple sketch which uses both libraries to get midi data from an external keyboard and sends it to the Fluxamasynth shield:

#include <NewSoftMIDI.h>
#include <Fluxamasynth_NSS.h>
#include <NewSoftSerial.h>

Fluxamasynth synth; // uses pin4 for midi-in and no pin for midi-out
NewSoftMIDI midi(7,255); // uses pin 7 for midi-in and no out pin 

void HandleNoteOn(byte channel, byte pitch, byte velocity)
{
  if (velocity == 0)
  {
    HandleNoteOff(channel,pitch,velocity);
  }
  else
  {
    synth.noteOn(channel,pitch,velocity);
  }
}

void HandleNoteOff(byte channel, byte pitch, byte velocity)
{
  synth.noteOff(channel,pitch);
}

void setup()
{
  synth.programChange(0, 0, 40);
  midi.begin(MIDI_CHANNEL_OMNI);    
  midi.setHandleNoteOn(HandleNoteOn);  
  midi.setHandleNoteOff(HandleNoteOff);  
}

void loop()
{
  midi.read();    
}
Note: This is currently untested!

If someone likes to test the NewSoftMIDI library, please contact me!