Monthly Archives: March 2018

AK47m Project Part 4 (Making sense of Roland style files)

I have installed Lazarus IDE and Free Pascal on my Windows PC. The installation is easy and it works out of the box !. There is a function for converting Delphi projects to Lazarus projects. The BK7m performance editor I wrote several years back was a Delphi project. I converted this to Lazarus and it required a few tweaks to get it working but nothing major. The advantage of doing this is that I can develop software more efficiently on the PC and then transfer the projects to a Raspberry PI and compile them with the native Lazarus system. Most objects can be transferred easily, but obviously some of the interfaces to MIDI and ALSA need to be tweaked.

One of the last features I was working on for the BK7m editor was a style file editor, this was shelved due to other work, however I started looking at the work completed as a basis for the AK47m project. There is scant information on the Roland style (.stl) file format around, so I had to do some reverse engineering on the format. Any extra information would be welcome.

Style file (.STL) format

The style file basically has a header block at the start followed by a heap. The header contains tables of indexes into the heap and tables of corresponding object sizes. Each object in the heap, which I have termed as a “snippet” contains a list of MIDI commands and time offsets as in a MIDI file. The “snippet” can be played to a MIDI device and it will play a 1..n bar phrase for a single instrument.

Snippets are created for up to 8 instruments (Drums, Bass, 6 harmony instruments). There are three versions of a snippet for an instrument, namely Major, Minor and Seventh. Each snippet is in the key of C, a Major snippet will play a musical phrase for C Major, and a Seventh snippet will play a musical phrase for C7. The arranger software identifies what chord is being played with the left hand and then applies offsets to the pitches of a snippet to play harmony corresponding to the chord. i.e. transposing C to G would add 7 semitones to each note in the snippet. Some snippets are used for the drum track and these would not get transposed at all.

I asked myself about what happens when a Major Seventh chord is played. i.e. C E G B. I assume that the Seventh harmony snippet (C E G Bb)  is used as a basis and a transformation is applied that converts the Bb to B natural before the pitch offset is applied. Similarly a transformation to get a Cm7 harmony (C Eb G Bb) would convert the E natural to Eb.

One thing I wondered about was whether the simple Major and Minor snippets are necessary since a Major could be obtained by dropping the Bb from the Seventh snippet. Maybe the Major snippet is used to produce a Sixth harmony ( C E G A) by adding a tone above the fifth note. More information on this would help me !!

There is a higher level grouping which I have named as sections. There are 16 sections. The structure is 16 sections of 8 instruments of 3 harmonies. The sections correspond to the variations, intro, outro and fills. Some of the sections are completely empty which is a bit of a puzzle.

I believe that the sections are used as follows:

  1.  Intro 3
  2.  Variation 1
  3.  Variation 3
  4. — spare —
  5. 1 bar fill Variation 2
  6. 1 bar fill Variation 1
  7. — spare —
  8. Outro 4
  9.  Intro 4
  10.  Variation 2
  11. Variation4
  12. — spare —
  13. 1 bar fill Variation 4
  14. 1 bar fill Variation 3
  15. — spare —
  16. Outro 3

There is also a table of Word values for each snippet (snippetUnknown), what this relates to I am unsure – are they flags or offsets. Help required.

Unfortunately my BK7m unit does not work so I am unable to compare what the BK7m does with each snippet.

What puzzles me about this is that there are no snippets for intro 1 & 2 and outro 1 & 2.

On closer inspection of the file, I have discovered that there is another header record that starts with G8<name> . It seems that the header maps to the same structure as the first header. There doesn’t seem to be any record size fields, so I have to search for the string G8<name> in the raw data to find the start of the second record. This format is unbelievable spaghetti.

The snippets missing from the first record are in the second record. Note that the snippet pointers are relative to the start of the record in the file.

I suspect that the second record was added for backwards compatibility. There looks like a third record related to the one touch settings. ( i wondered where these were in the BK7 performance file !)

 

styleHeader = record

id: packed array [1..2] of char;
 name: packed array [0..15] of char;
 d1: packed array [1..2] of byte;
 tempo: packed array [0..1] of byte;
 d2: packed array [1..2] of byte;
 timeSignatures: packed array [0..15] of timeSig;
 d3: packed array [1..2] of byte;


 snippetPointers: packed array [0..7] of //instrument
 packed array [0..15] of //section
 packed array [0..2] of longint; //maj min 7th

snippetUnknown: packed array [0..7] of //instrument
 packed array [0..15] of //section
 packed array [0..2] of word; //maj min 7th

snippetSizes: packed array [0..7] of //instrument
 packed array [0..15] of //section
 packed array [0..2] of word; //maj min 7th

 end;

 

“id” is set to the string “G8”.

The snippet pointers reference a list of 6 byte records that have the MIDI data and timing data :-

 

type

timeSig = record
 beatsPerMeasure: byte;
 beatValue: byte; // 01 = 1/2 02=1/4 03= /8
 end;

noteEvent = record
 channel: byte;
 velocity: byte;
 dollar40: byte;
 timeLength: byte;
 end;

controllerEvent = record
 channel: byte;
 effect: packed array[0..2] of byte;
 end;

pitchWheelEvent = record
 channel: byte;
 pitchTimesThree: packed array[0..2] of byte;
 //pitchWheelCommandAgain : byte;
 end;


 programChangeEvent = record
 channel: byte;
 prog: packed array[0..2] of byte;
 end;

nrpnEvent = record
 channel: byte;
 msb: byte;
 lsb: byte;
 Data: byte;
 end;

eventRecord = packed record
 timeIncrement: byte;
 case pitchOrCommand: byte of
 0: (raw: packed array [0..3] of byte);
 1: (note: noteEvent);
 $E6: (controller: controllerEvent);
 $EB: (pitchWheel: pitchWheelEvent);
 $E5: (programChange: programChangeEvent);
 $EA: (nrpn: nrpnEvent);
 $8F: (); // end of list
 $80: (); //data ??

end;


 eventList = array of eventRecord;


Project Goals

The BK7m Editor can interface to a PC MIDI device and output MIDI commands. When MIDI is connected, it is possible to load a style file and play the individual snippets or any combination of them. This was helpful in deciphering the file structure.

The next project goals are to write a PC based program based on the BK7m editor that can perform the MIDI arranger functions. There are five parts to this:

  1. Create general purpose MIDI input output module that works on Windows and PI.
  2. Load a selected style file into appropiate internal data structures.
  3. Identify the chord harmony from notes played on the lower MIDI keyboard and output the chord name.
  4. Apply transformations of snippets using (3) and play the MIDI notes.
  5. Handle transitions between variations with appropriate fills.

 

 


			
		

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.

 

AK47m Project Part 2 (Reading MIDI into Raspberry Pi)

THE ORIGINAL POST HAS BEEN SUPERCEDED. I Have left it at the end in small print..

 

The PIC MIDI interface allows me to connect to a MIDI keyboard and also provide MIDI test messages using a keypad. The output of the UART was connected to the RXD input on the PI Zero. The next part had the following goals :

  1. Configure PI Zero to accept MIDI messages
  2. Provide simple test scripts to check data is arriving in tact.
  3. Install Lazarus and Free pascal to develop PASCAL programs on the PI.
  4. Write a simple PASCAL test program to receive MIDI messages and display them.

 

There is some trickery involved in getting the PI to accept MIDI input, there is a lot of old stuff relating to this out on tinternet. Hopefully this is THE CORRECT WAY TO DO IT !!!

The trickery involves setting up the UART connected to the pin header, moving a UART to the bluetooth hardware and altering the UART master clock so that a legal baud rate of 38400 actually turns out to be 31250. Once the configuration is done, the UART is accessible as /dev/serial0 and needs to be configured with a baud rate of 38400.

**NOTE** ttyAMA0 has now been dropped and the device is now called /dev/serial0 . So I’ve updated these so that my software is compatible with the PI Zero W and the PI B+.

There was also a stupid gotcha that confused me. If a command line terminal (tty) is configured, it will try to use the same port and cause absolute misery. So the answer is to DISABLE THE COMMAND LINE TERMINAL USING RASPI-CONFIG.

Step 1:

Ensure the PI ZERO W operating system is latest and up to date.

Step 2:

Edit the file /boot/config.txt.

e.g. sudo leafpad /boot/config.txt

Add the following lines at the end.

enable_uart = 1
dtoverlay = pi3-miniuart-bt
dtoverlay = midi-uart0

Step 3:

Ensure the command line terminal is not attached to UART.

Run the configuration utility:

sudo raspi-config

Select option 5 “Interfacing Options”, then select option P6 “Serial”. Answer “No” to “do you want to have a login shell on the serial port ?”

Step 4:

Check the comms. First install the Minicom software.

sudo apt-get install minicom

Then run the Minicomm terminal.

minicom -b 38400 -H -D /dev/ttyAMA0

This will show any received data in hexadecimal format on the screen. Simply press the keypad to send a MIDI message and check it is displayed correctly. At this point the PI is ready to accept MIDI data. The next steps deal with developing programs to access the port.

Step 5:

Install Free pascal and Lazarus IDE.

sudo apt-get install fpc
sudo apt-get install lazarus

The Lazarus IDE is then available from the “Programming” option on the main applications menu from the desktop. Create a new project and check that a hello world program can be written.

Step 6:

We are now in a position to write a MIDI test program in PASCAL. I am using the native Lazarus serial device interface routines. I wrote a Lazarus console application to test the port and the code is as follows. The test program prints out received data as hex to the console window until a key is pressed.

program MidiTest;

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

var

  serialPortHandle: TSerialHandle;
  count: integer;
  receiveBuffer: array [1..255] of byte;


begin

  try
    writeln('MIDI test program');

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

    writeln;
    writeln;
    writeln('Port opened. Press key to exit');

    while True do
    begin
      try

        count := SerReadTimeout(serialPortHandle, receiveBuffer, 1);

        if (count > 0) then
            Write(intToHex(byte(receiveBuffer[1]), 2) + ' ');

        if keyPressed then
          break;

      except
        break;
      end;
    end;


  finally
     serClose(serialPortHandle);
  end;

end.

 

 

 

 

SUPERCEDED……………..

The PIC MIDI interface allows me to connect to a MIDI keyboard and also provide MIDI test messages using a keypad. The output of the UART was connected to the RXD input on the PI Zero. The next part had the following goals :
  1. Configure PI Zero to accept MIDI messages
  2. Provide simple test scripts to check data is arriving in tact.
  3. Install Lazarus and Free pascal to develop PASCAL programs on the PI.
  4. Write a simple PASCAL test program to receive MIDI messages and display them.

There is some trickery involved in getting the PI to accept MIDI input, there is a lot of old stuff relating to this out on tinternet. Hopefully this is THE CORRECT WAY TO DO IT !!!

The trickery involves setting up the UART connected to the pin header, moving a UART to the bluetooth hardware and altering the UART master clock so that a legal baud rate of 38400 actually turns out to be 31250. Once the configuration is done, the UART is accessible as /dev/ttyAMA0 and needs to be configured with a baud rate of 38400.

**NOTE** ttyAMA0 has now been dropped and the device is now called /dev/serial0 . So I’ve updated these so that my software is compatible with the PI Zero W and the PI B+.

There was also a stupid gotcha that confused me. If a command line terminal (tty) is configured, it will try to use the same port and cause absolute misery. So the answer is to DISABLE THE COMMAND LINE TERMINAL USING RASPI-CONFIG.

Step 1:

Ensure the PI ZERO W operating system is latest and up to date.

Step 2:

Edit the file /boot/config.txt.

e.g. sudo leafpad /boot/config.txt

Add the following lines at the end.

enable_uart = 1 dtoverlay = pi3-miniuart-bt dtoverlay = midi-uart0

Step 3:

Ensure the command line terminal is not attached to UART.

Run the configuration utility:

sudo raspi-config

Select option 5 “Interfacing Options”, then select option P6 “Serial”. Answer “No” to “do you want to have a login shell on the serial port ?”

Step 4:

Check the comms. First install the Minicom software.

sudo apt-get install minicom

Then run the Minicomm terminal.

minicom -b 38400 -H -D /dev/ttyAMA0

This will show any received data in hexadecimal format on the screen. Simply press the keypad to send a MIDI message and check it is displayed correctly. At this point the PI is ready to accept MIDI data. The next steps deal with developing programs to access the port.

Step 5:

We install Python and run a Python script to do the same as Step 4. There is method in this madness.

Firstly install Python and its serial library:

sudo apt-get install python sudo apt-get install python-serial

Edit a file e.g. /home/pi/midi-test and copy the following script:

#!/usr/bin/env python import serial print ‘midi test’ ser = serial.Serial(‘/dev/ttyAMA0’, baudrate=38400, timeout=10) while True: data = ord(ser.read(1)) # read a byte print (str(data) + ‘ ‘)

Make the script executable

chmod a+x midi-test

Run the script and then send MIDI messages.

./midi-test

It should print out the decimal values of each byte and timeout if nothing is received after 10 seconds.

Step 6:

Install Free pascal and Lazarus IDE.

sudo apt-get install fpc sudo apt-get install lazarus

The Lazarus IDE is then available from the “Programming” option on the main applications menu from the desktop. Create a new project and check that a hello world program can be written.

Step 7:

We are now in a position to write a MIDI test program in PASCAL. I downloaded the “Synapse” communications library from http://www.ararat.cz/synapse/doku.php/download

and copied the source to a suitable folder searchable by Lazarus. This library is compatible with Windows and Unix. I only needed the serial comms part. I may have been the first person to try this library on a PI Zero and encountered some problems.

There are some demo programs. I converted the Delphi modem demo program Synapse/source/demo/modem/ModemDemo.dpr to a Lazarus project and compiled it. There was a compile error in the unit synaser.pas because of some undefined high baud rates for the UNIX build. I hacked this to work as below:

const {$IFDEF UNIX} {$IFDEF DARWIN} MaxRates = 18; //MAC {$ELSE} MaxRates = 30-11; //UNIX {$ENDIF} {$ELSE} MaxRates = 19; //WIN {$ENDIF} Rates: array[0..MaxRates, 0..1] of cardinal = ( (0, B0), (50, B50), (75, B75), (110, B110), (134, B134), (150, B150), (200, B200), (300, B300), (600, B600), (1200, B1200), (1800, B1800), (2400, B2400), (4800, B4800), (9600, B9600), (19200, B19200), (38400, B38400), (57600, B57600), (115200, B115200), (230400, B230400) {$IFNDEF DARWIN} ,(460800, B460800) {$IFDEF UNIX} { ,(500000, B500000), (576000, B576000), (921600, B921600), (1000000, B1000000), (1152000, B1152000), (1500000, B1500000), (2000000, B2000000), (2500000, B2500000), (3000000, B3000000), (3500000, B3500000), (4000000, B4000000) } {$ENDIF} {$ENDIF} ); {$ENDIF}

The second problem encountered was that the port configuration was not set correctly. The Synapse call to do this should be:

ser.Config(38400,8,’N’,1,false,false);

but the port is not set correctly. The work around to this is to currently call a Python script to just open the port. The configuration settings seem to persist if the ser.Config call is ignored. The python script “hack-uart-params” is as follows. Note that a baudrate of 38400 is really 31250 due to trickery :

#!/usr/bin/env python import serial print ‘hacking serial port’ ser = serial.Serial(‘/dev/ttyAMA0’, baudrate=38400, timeout=1)

I wrote a Lazarus console application to test the port and the code is as follows. The test program prints out received data as hex to the console windo until a key is pressed.

program MidiTest; {$mode objfpc}{$H+} uses Classes, SysUtils, synaser, Unix, Crt; var ser:TBlockSerial; i, count : integer; buf : AnsiString; begin ser := TBlockSerial.Create; ser.RaiseExcept := True; try writeln (‘MIDI test program’); writeln (‘Removing Lock’); writeln; fpSystem (‘rm /var/lock/LCK..ttyAMA0’); writeln; ser.Connect(‘/dev/ttyAMA0’); writeln(‘Setting port config’); writeln; //ser.Config(38400,8,’N’,1,false,false); fpSystem (‘./hack-uart-params’); writeln; writeln; writeln (‘Port opened. Press key to exit’); while true do begin try count := ser.WaitingData; if (count > 0) then begin buf := ser.RecvBufferStr(count,1000); for i:= 1 to count do write ( intToHex(byte(buf[i]),2) + ‘ ‘); end; if keyPressed then break; except break ; end; end; finally ser.free; end; end.

AK47m Project Part 1 (MIDI interface and Keypad interface)

This is a proof of concept project to produce a Roland BK7m replacement with a similar piece of kit that costs under £50. (which is the price an engineer would charge to look at a faulty BK7m.) This was outlined in an “up yours Roland” post before. A suitable name for this is the AK47m and you can work it out yourself why I chose it !

The first part of the project is to develop an interface to a MIDI instrument. The proper way to interface to MIDI input is to use an opto-isolator to avoid any direct electronic connections that could cause earth loops and to filter out any other nasty transients.

My first prototype used a general purpose opto-isolator and this failed miserably. It produced the appropriate changes in logic levels in response to an input MIDI message, but it was not fast enough. When I looked at the output from the opto it had a rounded return to +3.5v and the level was about half way at the bit sampling point, so it was neither up or down. So I need to order a faster device.

In order to make progress, I used a direct connection to the MIDI instrument and this worked good enough for my prototype, however I shall modify it to use an opto in due course.

I have used a PIC 16F628 microcontroller to handle the MIDI input and also to handle a 3×4 matrix keypad. The PIC outputs MIDI messages from its internal UART which are directed to any number of Raspberry PI computers. The PIC scans the keypad and debounces key presses. These are then translated into MIDI system common messages. e.g. a key press of 5 results in three MIDI bytes of

F1 00 05

An additional 4 press switches will be included later and these will be driven by the A3 output (A0, A1 & A2 drive the keypad, B4 ,B5, B6 and B7 sense the keypad)

 

Messages received from the instrument are relayed onwards. This introduces a slight amount of latency but that’s ok.

The PIC operates at 4 MHz and has a ceramic resonator to implement the clock, however it works just as well using the internal clock oscillator. The MIDI message baud rate is 31250 and so the bit time is 1000,000 / 31,250 =  32 uS. The internal UART for the PIC divides down the CPU oscillator to produce a UART clock and the divisor is a pure integer so the UART clock error is neglible.

The PIC firmware is written in PASCAL and compiled using the excellent MikroPascal IDE described previously. The firmware sets up the internal UART to receive and transmit at 31250 baud. It implements an interrupt handler that is activated when a single MIDI byte is received. The byte is stored in a FIFO buffer. The main process checks for received bytes and transmits them if present. It also scans the keypad matrix and debounces the key presses. If a key press is detected, a system message is transmitted with the keypad value. When the * key is pressed it transmits a MIDI message sequence that plays the three notes G,Ab,A as a test routine. A copy of the firmware is included later.

This is the circuit diagram – Note the opto isolater is shown seperately and the main circuit shows a direct connection

These are the connections to a 5 way DIN connector shown from the rear.

 

This is the keypad and the pinout

This shows the connections to the PI Zero . From left to right, RXD, Ground, 3.5v. The PI powers the PIC on the 3.5v supply line.

 

This is everything lashed up :

 

This is the PIC 16F628 firmware

program PI ;

{ Declarations section }
var fifo : array [0..63] of byte;
var inix, outix : byte;
var i : word;
var tmp : byte;


procedure interrupt(); iv 0x0004; ics ICS_AUTO;
begin
 if TestBit (PIR1, RCIF) then
 begin
 // tmp := RCSTA; // clear interrupt
 fifo[inix] := RCREG;


 if TestBit (RCSTA,FERR) or TestBit (RCSTA,OERR) then
 RCSTA := $90;

inix := (inix + 1) and $1F;

ClearBit(PIR1, RCIF);
 end;
end;

procedure delay ( pDelay : word) ;
//delay ms
var
 n : word;
 i : word;
begin
 for n := 1 to pDelay do
 for i := 1 to 76 do;
end;

procedure transmit ( pByte : BYTE) ;
begin
 TXREG := pByte;

//wait tx empty
 while true do
 begin
 if ((TXSTA and $02) = $02) then break;
 end;

end;

procedure sendTestMessage;
var
 i : byte;
begin
 for i := 0 to 2 do
 begin
 //note on
 transmit ($90);
 transmit (55 + i);
 transmit (80);

delay(150);

//note off
 transmit ($80);
 transmit (55 + i);
 transmit (0);
 end;
end;


procedure sendPerformanceMessage (performance : word);
const
 CONTROLCHANGE = $B0;
 PROGRAMCHANGE = $C0;
var
 cc32 , pc : word ;
begin
 cc32 := performance div 128;
 pc := performance mod 128;

transmit ($F1);
 transmit (cc32);
 transmit (pc);
end;


procedure busyDelay (delayMs : word);
var
 midiByte : byte;
 counter : dword;
begin
 // receive midi byte
 counter := delayMs * 30;
 while true do
 begin
 while (inix <> outix) do
 begin
 midiByte := fifo[outix];
 outix := (outix + 1) and $1F;
 transmit (midiByte);
 end;

if counter = 0 then break;
 counter := counter - 1;
 end;

end;

function scanKeypad : byte;
{

1 2 3 -- B7
 4 5 6 -- B4
 7 8 9 -- B5
 * 0 # -- B6

| | |
 | | |
 A0 A1 A2

* = 10
# = 11
}
var
 scan : byte;
begin
 scan := 255;

// OUT A2
 PORTA := $04;
 case (PORTB and $F0) of
 16 : scan := 6;
 32 : scan := 9;
 64 : scan := 11;
 128 : scan := 3;
 end;
 busyDelay(0);

// OUT A1
 PORTA := $02;
 case (PORTB and $F0) of
 16 : scan := 5;
 32 : scan := 8;
 64 : scan := 0;
 128 : scan := 2;
 end;
 busyDelay(0);

// OUT A0
 PORTA := $01;
 case (PORTB and $F0) of
 16 : scan := 4;
 32 : scan := 7;
 64 : scan := 10;
 128 : scan := 1;
 end;
 busyDelay(0);

scanKeypad := scan;
end;


function debounceKey : byte;
var
 key : byte;
 counter : word;
 presses : byte;
begin

// check for three consective valid presses 10ms check time
 counter := 0;
 presses := 0;
 while true do
 begin
 key := scanKeypad ;
 if (key <> 255) then
 presses := presses + 1
 else
 presses := 0;

if (presses = 3) then
 break; //valid key

busyDelay(10);
 counter := counter + 1;

if counter > 100 then
 begin
 key := 255;
 break; // timeout
 end;
 end;

// check for a release time of 50 ms
 counter := 0;
 presses := 0;
 if (key <> 255) then
 while true do
 begin
 if (scanKeypad = 255) then
 presses := presses + 1
 else
 presses := 0;

if (presses = 5) then
 break; //valid off time

busyDelay(10);
 counter := counter + 1;

if counter > 100 then
 begin
 key := 255;
 break; // timeout
 end;
 end;

debounceKey := key;
end;

begin
 { Main program }

// init usart
 TRISA := $FF; //isolate
 TRISB := $FF; // usart pins must be inputs exept B4
 CMCON := $07; // disable comparators


 //enable uart receiver and fifo
 RCSTA := $90;
 TXSTA := $24; // configure hi speed @ 4mhz
 SPBRG := 7; // MIDI baud rate 31250 ( 4000000 / 16 * BR) - 1

inix := 0;
 outix := 0;

//enable receiver interrupts
 SetBit (PIE1, RCIE);
 SetBit (INTCON, PEIE);
 SetBit (INTCON, GIE);

// output bit 0,1,2 and 4
 // 0,1,2 are for keypad scan, 4 is to control LED
 TRISA := $E8; // bit set is an input
 busyDelay(100);

inix := 0;
 outix := 0;

while true do
 begin
 busyDelay(0);

tmp := debounceKey; //times out after 1 second
 if (tmp <> 255) then
 begin
 if (tmp = 11) then 
 sendTestMessage
 else
 sendPerformanceMessage (tmp);
 end;

//PORTA := inix mod 3;
 end;

end.