AK47m Project Part 3 (Creating ALSA virtual device, interface to FluidSynth on Pi)

So I now have a MIDI hardware interface, the key pad control and a test program that processes MIDI data on the PI Zero W. My next goals are:

1 Obtain source code and demo code for developing an ALSA midi driver with Lazarus. There is an open source project FpALSA.

2 Create the demos using Lazarus development system.

3 Download and install Fluidsynth. Connect to demo driver.

4 Adapt the ALSA midi driver to accept MIDI data from my interface.

5 Connect my ALSA MIDI driver to Fluidsynth and test synth output from my MIDI keyboard.

Step 1:

Download the ALSA interface code from https://sourceforge.net/projects/fpalsa/

Extract the source to a suitable folder accesible to the Lazarus system. There is a source file called asoundlib.pp that is included as unit named asoundlib.

Step 2:

The fpalsa lib does not contain any example demo code. I found an older set of interface code and demo code at http://alsapas.alturl.com/ . I took one of the demos and got it to compile with the fpAlsa library with small mods. This code creates a virtual midi driver and sends raw key on and off messages out. The demo program code is given here. When it is compiled and running it creates a virtual MIDI driver.

program AlsaTest1;

{$mode objfpc}{$H+}

uses
 Classes,
 SysUtils,

Crt,
 asoundLib,
 Unix;

type
 TMidiMsg = array[0..2] of byte;

const
 note_on: TMidiMsg = ($90, 60, 100);
 note_off: TMidiMsg = ($80, 60, 0);

var
 device_out: PChar;
 handle_out: psnd_rawmidi_t;
 params: psnd_rawmidi_params_t;

err: integer;

begin

device_out := 'virtual';

writeln('Testing output rawmidi device ');

err := snd_rawmidi_open(nil, @handle_out, device_out, 0);
 if (err <> 0) then
 begin
 writeln('snd_rawmidi_open ', device_out, ' failed: ', err, ' ', snd_strerror(err));
 end
 else
 begin

snd_rawmidi_params_malloc(@params);

snd_rawmidi_params_current(handle_out, params);
 snd_rawmidi_params_set_no_active_sensing(handle_out, params, 1);
 snd_rawmidi_params(handle_out, params);
 snd_rawmidi_params_free(params);

writeln('Connecting synth');

// do alsa connection to active synth - one of these will work!!
 fpSystem('aconnect 128:0 129:0');
 fpSystem('aconnect 129:0 128:0');

writeln('Writing note on / note off');
 while not keypressed do
 begin
 snd_rawmidi_write(handle_out, @note_on, SizeOf(note_on));
 snd_rawmidi_drain(handle_out);
 sleep(1000);
 snd_rawmidi_write(handle_out, @note_off, SizeOf(note_off));
 snd_rawmidi_drain(handle_out);
 end;

writeln('Closing');
 snd_rawmidi_drain(handle_out);
 snd_rawmidi_close(handle_out);
 end;

end.

step 3:

Download and install “QSynth“. This is a GUI to control “FluidSynth“. FluidSynth uses sampled sounds in a soundfont file to produce sounds in response to received MIDI messages. The download includes QSynth, FluidSynth and the soundfont file called “FluidR3_GM.sf2”.

sudo apt-get install qsynth

Run Qsynth from the main application menu under Sound & Video. A panel is displayed to control the synthesizer. Click on the “Setup” button. There are tabs to set the MIDI, Sound Output and Soundfont.

The following screens show what set up is required :

Press OK and the new settings will take effect. It takes a while to load the soundfont and you should wait until the buttons become active before proceeding.

Open a command terminal and enter the following:

aconnect -l

You will see a list of active MID devices. e.g.

pi@PIZERODEV:~/Peter/Midi $ aconnect -l
client 0: 'System' [type=kernel]
 0 'Timer '
 1 'Announce '
client 14: 'Midi Through' [type=kernel]
 0 'Midi Through Port-0'
client 128: 'Client-128' [type=user,pid=1134]
 0 'Virtual RawMIDI '

In my example the QSynth is client 128:0 .

When I run the demo program AlsaTest1 created above it creates a new device as client 129:0 .

You will see the command

aconnect 129:0 128:0

executed as part of the demo program. This connects the output of the demo program to the input of the QSynth. Run AlsaTest1 and you should hear a repeated piano note played until a key is pressed.

Steps 4 & 5 :

I have basically combined the program in part 2 with AlsaTest1 to produce AlsaTest2. This program connects the UART input to QSynth and allows me to run test sequences from the keypad and play notes on my MIDI keyboard.

There is some more work required here to refine the program and performance but the main principle is outlined and working.

This program runs QSynth and waits for the return key to be pressed when it is fully loaded. It will then make the required connection and link the UART input to the Synthesizer until a key is pressed at the terminal.

program AlsaTest2;

{$mode objfpc}{$H+}
uses
  Classes,
  SysUtils,
  Crt,
  asoundLib,
  Unix,
  Serial;

var
  device_out: PChar;
  handle_out: psnd_rawmidi_t;
  params: psnd_rawmidi_params_t;
  err: integer;

  serialPortHandle: TSerialHandle;
  i, Count: integer;
  receiveBuffer: array [1..255] of byte;

begin
  ser := TBlockSerial.Create;
  ser.RaiseExcept := True;

  device_out := 'virtual';
  try
    writeln('MIDI test program');

    serialPortHandle := SerOpen('/dev/serial0');
    SerSetParams(serialPortHandle, 38400, 8, NoneParity, 1, []);

    writeln;
    writeln;

    // create ALSA device channel
    err := snd_rawmidi_open(nil, @handle_out, device_out, 0);
    if (err <> 0) then
    begin
      writeln('Open ALSA failed: ', err, ' ', snd_strerror(err));
    end
    else
    begin
      // ALSA channel setup
      snd_rawmidi_params_malloc(@params);
      snd_rawmidi_params_current(handle_out, params);
      snd_rawmidi_params_set_no_active_sensing(handle_out, params, 1);
      snd_rawmidi_params(handle_out, params); snd_rawmidi_params_free(params);
      writeln; writeln ('Starting synth .... press enter to continue when running ...');
      fpSystem ('qsynth &');
      readln;
      // do alsa connection to active synth
      // a hack- one of these will work
      writeln('Connecting to synth');
      fpSystem('aconnect 128:0 129:0');
      //fpSystem('aconnect 129:0 128:0');
      try
        writeln;
        writeln ('Main processing loop. Press a key to exit');
        while True do
        begin

          // very inefficient read of 1 character at a time
          count := SerReadTimeout(serialPortHandle, receiveBuffer, 1);
          if (count > 0) then
          begin
            snd_rawmidi_write(handle_out, @receiveBuffer[1], Count);
            snd_rawmidi_drain(handle_out);
          end
          else sleep(0);
          if keyPressed then
          break;
        end;
      finally
         writeln('Closing ALSA. Dont forget to QUIT synth');
         snd_rawmidi_drain(handle_out);
         snd_rawmidi_close(handle_out);
      end;
    end;
  finally
      serClose(serialPortHandle);
  end;
end.

 

Leave a Reply