Re: [yoshimi-user] MIDI program change and bank change

  • From: Andrew Deryabin <andrewderyabin@xxxxxxxxx>
  • To: yoshimi-user@xxxxxxxxxxxxxxxxxxxxx
  • Date: Fri, 10 May 2013 00:53:23 +0000

08.05.2013 19:59, Will J Godfrey пишет:

On Wed, 08 May 2013 23:31:18 +0000
Andrew Deryabin <andrewderyabin@xxxxxxxxx> wrote:

08.05.2013 18:40, Kristian Amlie пишет:
On 05/06/13 16:38, Will J Godfrey wrote:
Now some thoughts about bank change.

As these don't currently have a numerical prefix I suggest that we only
implement this for banks who's names are changed to include a prefix. Once
again, this doesn't mess with existing Yoshi. versions. As before, calling for
a bank number that doesn't exist should be ignored, with just a warning to
stdout.

The real problem is that the MIDI spec is slightly ambiguous.

if you examine the MIDI spec it says that LSB is not commonly used. However in
the *specific* case of bank changes is says the LSB *is* more commonly used.
My knowledge is slightly fuzzy here, but I think what they mean is that
controller messages have an optional MSB/LSB variant that can be used in
place of traditional control messages (check out controllers
6/38/100/101 at http://www.voidaudio.net/controller.html). But this is
hardly supported by anyone (I have never seen a synth that uses it).

Bank select MSB and LSB are however very common, for example FluidSynth
uses them, and all the devices I have support it.

I can't seem to find consistent information about what hardware synth
manufacturers actually use, so I would suggest we go with the spec and number
from 1 to 128. If we *really* wanted to hedge our bets, then we could also
implement the USB translating the numbers as:
0
128
256
384
512
etc.

but do we actually need to cover 16384 banks!
IMHO, we should just stick to counting 0, 1, 2, 3.... up to 127 for LSB,
and then starting at 128 once MSB turns over to 1. That would
theoretically cover 16384 banks, but we don't actually have to enforce
all those digits. Sticking to three or four digits should be enough, and
it won't stop the courageous ones from exceeding it.

Hi!

I've already coded midi patch change handling yesterday :).It works now
- bank numbers are handled from 0 to 16383, programs - from 0 to 127
(the code is very simple - only 10 lines for midi messages processing).
Next I'll do optimal algorythm for patch searching and loading from bank
dirs and instrument files (to eat less cpu and disk resources). In a few
days I'll post instructions how to test all of that :).
Impressive :)

Hello, all!

So, here is a patch for midi bank select and program change.

It's should be applyed to current master git branch 1.1.0:

git clone git://yoshimi.git.sourceforge.net/gitroot/yoshimi/yoshimi
git apply 0001-Support-for-midi-program-and-bank-change.patch

A little excursion in midi controller messages 0xB0 type 0x0 (bank select MSB) and 0xC0 (program select):

I've tested yoshimi with my patch on ardour and rosegarden configurations - both work the same way:

1. Send controller 0xB0 type 0x0 (bank select MSB) to synthesizer with third byte = MSB for bank select.
2. Send controller 0xB0 type 0x0 with third byte = LSB for bank select.
3. Send controller 0xC0 (midi program change) with second byte = program number (from 0 to 127).

Final bank number = MSB*128 + LSB (will be from 0 to 16383)

All numbers for banks and programs are counted from 0, so they are incremented by 1 when received.

Notes about the patch:

When all information about bank and program number was collected, Synth engine is called with new function 'SetBankAndProgram'. Here are the steps of what is going on there:

1. A loop started for all bank root dirs configured. Then for each root dir next steps are taken:
2. Iterate through all folders of the current root dir and search for mask '.*banknumber-.*'. Examples of matches for it are:
0016-Pads
0001-Synth
00034-Guitar
54-Voice

and so on..

It means that 0034-Guitar and 34-Guitar are the same for bank number 34 and will be processed together.

3. After bank dir is found, patch searching takes place (if dir is not found, log entry is written).
4. Patches are searched by the same algorythm as banks. So if we want to set patch number 105 then matches will be:

0105-Instrument1.xiz
105-Instrument1.xiz

The first one is selected for insertion.

5. Full path to instrument file is created and function 'loadXMLinstrument' is called for each part connected to channel for which program change is received.

6. GUI updated after all parts are processed.

In order to patch start working, banks should be numbered. Here is simple solution:

I've included simple bash script in patch called mkbanklinks.sh. After applying patch it will be in yoshimi root directory.
What it does:

1. Makes new directory (if not exists) 'yoshimibanks' in your home dir.
2. Copies recursive all folders from /usr/share/yoshimi/banks to that dir and adds number prefix to them starting from 0001-

Here is my list after executing this script:

0001-Arpeggios 0006-Drums 0011-Noises 0016-Rhodes 0021-Will_J_Godfrey_Collection
0002-Bass 0007-Dual 0012-Organ 0017-Splited
0003-Brass 0008-Fantasy 0013-Pads 0018-Strings
0004-chip 0009-Guitar 0014-Plucked 0019-Synth
0005-Choir_and_Voice 0010-Misc 0015-Reed_and_Wind 0020-SynthPiano

You should add that dir with numbered banks to the list of root bank dirs in yoshimi settings and restart it.

After all that - TEST, TEST, TEST! :)

I believe that my explanation was clear enough to start :)









From 1bbeb44b487f13ea96739a565e3e6d869f5c7411 Mon Sep 17 00:00:00 2001
From: Andrew Deryabin <andrewderyabin@xxxxxxxxx>
Date: Fri, 10 May 2013 03:05:38 +0400
Subject: [PATCH 1/1] Support for midi program and bank change

---
mkbanklinks.sh | 15 ++++++
src/Misc/SynthEngine.cpp | 123 +++++++++++++++++++++++++++++++++++++++++++++
src/Misc/SynthEngine.h | 1 +
src/MusicIO/JackEngine.cpp | 25 ++++++++-
src/MusicIO/MusicIO.cpp | 6 +++
src/MusicIO/MusicIO.h | 1 +
6 files changed, 170 insertions(+), 1 deletion(-)
create mode 100755 mkbanklinks.sh

diff --git a/mkbanklinks.sh b/mkbanklinks.sh
new file mode 100755
index 0000000..2744390
--- /dev/null
+++ b/mkbanklinks.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+cnt=1
+banksroot="/usr/share/yoshimi/banks"
+
+mkdir -p ~/yoshimibanks
+for dir in ${banksroot}/*
+do
+
+ dirbasename=`basename "${dir}"`
+ scnt=`printf '%04d' "${cnt}"`
+ cnt=`expr $cnt + 1`
+ cp -rvf "${dir}" ~/yoshimibanks/"${scnt}-${dirbasename}"
+
+done
diff --git a/src/Misc/SynthEngine.cpp b/src/Misc/SynthEngine.cpp
index 205098b..904bfad 100644
--- a/src/Misc/SynthEngine.cpp
+++ b/src/Misc/SynthEngine.cpp
@@ -27,6 +27,9 @@ using namespace std;

#include "MasterUI.h"
#include "Misc/SynthEngine.h"
+//these includes are for midi program change support
+#include <sys/types.h>
+#include <dirent.h>

SynthEngine *synth = NULL;

@@ -337,6 +340,126 @@ void SynthEngine::SetController(unsigned char chan,
unsigned int type, short int
}


+void SynthEngine::SetBankAndProgram(unsigned char chan, int bank, unsigned
char prog)
+{
+ //first get bank root dirs from runtime and try
+ //to find suitable bank dir with mask .*bank-.*
+ //using readdir here because midi messages are read in synchronous manner
+ bool bankFound = false;
+ bool patchFound = false;
+ string currentBankName;
+ string newPatchFilePath;
+ struct dirent *dirEntry;
+ for (int i = 0; i < MAX_BANK_ROOT_DIRS; i++)
+ {
+ string currentDir = Runtime.bankRootDirlist[i];
+ if (!currentDir.empty())
+ {
+ DIR *dirp = opendir(currentDir.c_str());
+ if (dirp == NULL)
+ {
+ Runtime.Log("SetBankAndProgram cant't open bank root dir: " +
currentDir + " for reading!");
+ continue;
+ }
+
+ while ((dirEntry = readdir(dirp)) != NULL)
+ {
+ currentBankName = dirEntry->d_name;
+ size_t pos = currentBankName.find('-');
+ if (pos != string::npos)
+ {
+ int bankNumber = string2int(currentBankName.substr(0,
pos));
+ if (bankNumber == bank)
+ {
+ //bank dir found
+ //next - search for new program
+ Runtime.Log("SetBankAndProgram found bank dir: " +
currentBankName);
+ bankFound = true;
+ break;
+
+ }
+ }
+ }
+
+ closedir(dirp);
+ if (bankFound)
+ {
+ string newBankDir = currentDir;
+ if (newBankDir.at(newBankDir.length() - 1) != '/')
+ {
+ newBankDir += '/';
+ }
+ newBankDir += currentBankName;
+
+ dirp = opendir(newBankDir.c_str());
+ if (dirp != NULL)
+ {
+ while ((dirEntry = readdir(dirp)) != NULL)
+ {
+ string currentPatchName = dirEntry->d_name;
+ size_t pos = currentPatchName.find('-');
+ if (pos != string::npos)
+ {
+ int patchNumber =
string2int(currentPatchName.substr(0, pos));
+ if (patchNumber == (int) prog)
+ {
+ //new patch found
+ //next - make full file path, exit main loop
and try to load it
+ newPatchFilePath = newBankDir + "/" +
currentPatchName;
+ Runtime.Log("SetBankAndProgram found new
patch! Full path is: " + newPatchFilePath);
+ patchFound = true;
+ break;
+
+ }
+ }
+ }
+
+ closedir(dirp);
+ }
+ else
+ {
+ Runtime.Log("SetBankAndProgram cant't open bank dir: " +
newBankDir + " for reading!");
+ }
+ }
+ else
+ {
+ Runtime.Log("SetBankAndProgram can't find dir for bank " +
asString(bank));
+ }
+
+ if (bankFound && patchFound)
+ {
+ //exit main loop
+ break;
+ }
+
+ }
+ }
+
+ if (bankFound && patchFound)
+ {
+ //try to load new patch
+ for (int npart = 0; npart < NUM_MIDI_PARTS; ++npart)
+ { // Send the controller to all part assigned to the channel
+ if (chan == part[npart]->Prcvchn && part[npart]->Penabled)
+ {
+ part[npart]->loadXMLinstrument(newPatchFilePath);
+ }
+ }
+ //update UI
+ guiMaster->updatepanel();
+ if (guiMaster->partui && guiMaster->partui->instrumentlabel &&
guiMaster->partui->part)
+ {
+
guiMaster->partui->instrumentlabel->copy_label(guiMaster->partui->part->Pname.c_str());
+ }
+ }
+ else
+ {
+ Runtime.Log("program " + asString(prog) + " in bank " + asString(bank)
+ " for channel " + asString(chan) + " not found");
+ }
+
+}
+
+
// Enable/Disable a part
void SynthEngine::partonoff(int npart, int what)
{
diff --git a/src/Misc/SynthEngine.h b/src/Misc/SynthEngine.h
index 168ed8b..37b4372 100644
--- a/src/Misc/SynthEngine.h
+++ b/src/Misc/SynthEngine.h
@@ -67,6 +67,7 @@ class SynthEngine : private SynthHelper, MiscFuncs
void NoteOn(unsigned char chan, unsigned char note, unsigned char
velocity);
void NoteOff(unsigned char chan, unsigned char note);
void SetController(unsigned char chan, unsigned int type, short int
par);
+ void SetBankAndProgram(unsigned char chan, int bank, unsigned char
prog);
float numRandom(void);
unsigned int random(void);
void ShutUp(void);
diff --git a/src/MusicIO/JackEngine.cpp b/src/MusicIO/JackEngine.cpp
index cdca380..afda647 100644
--- a/src/MusicIO/JackEngine.cpp
+++ b/src/MusicIO/JackEngine.cpp
@@ -431,10 +431,18 @@ void *JackEngine::midiThread(void)
unsigned int fetch;
unsigned int ev;
struct midi_event midiEvent;
+ //Patch change consists of 2 messages:
+ //1. Bank select (controler message 0x0) - 2 times (first message is for
msg, second - for lsb).
+ //2. Program change.
+ //lastBank is always set to 0 by default for every midi channel and after
program change.
+ //So if there is only one program change message (without bank change,
then default bank will be used)
+ int lastBank [16];
+ memset(lastBank, 0, 16 * sizeof(int));
+
if (sem_init(&midiSem, 0, 0) < 0)
{
Runtime.Log("Error on jack midi sem_init " + string(strerror(errno)));
- return false;
+ return (void *)0;
}

while (Runtime.runSynth)
@@ -506,6 +514,11 @@ void *JackEngine::midiThread(void)
case 0xB0: // controller
ctrltype = getMidiController(midiEvent.data[1]);
par = midiEvent.data[2];
+ if(ctrltype == 0) //bank select
+ {
+ lastBank[channel] = (lastBank[channel] << 7) | (par &
0x7F); //bank is selected from 0 to 16383 in 2 stages before program change
+ break;
+ }
setMidiController(channel, ctrltype, par);
break;

@@ -518,6 +531,16 @@ void *JackEngine::midiThread(void)
case 0xF0: // system exclusive
break;

+ case 0xC0: // program change
+ par = midiEvent.data [1] & 0x7F;
+ //bank and program start from 0, but we need them to start
with 1
+ lastBank[channel]++;
+ par++;
+ Runtime.Log("Program change on channel " +
asString((int)channel) + ": bank=" + asString(lastBank[channel]) + ", prog: "+
asString(par));
+ setMidiBankAndProgram(channel, lastBank[channel], par);
+ lastBank[channel] = 0; //set to 0 (default value)
+ break;
+
default: // wot, more?
Runtime.Log("other event: " + asString((int)ev));
break;
diff --git a/src/MusicIO/MusicIO.cpp b/src/MusicIO/MusicIO.cpp
index b3ea477..f369381 100644
--- a/src/MusicIO/MusicIO.cpp
+++ b/src/MusicIO/MusicIO.cpp
@@ -148,6 +148,12 @@ void MusicIO::setMidiNote(unsigned char channel, unsigned
char note)
}


+void MusicIO::setMidiBankAndProgram(unsigned char chan, int bank, unsigned
char prog)
+{
+ synth->SetBankAndProgram(chan, bank, prog);
+}
+
+
bool MusicIO::prepBuffers(bool with_interleaved)
{
int buffersize = getBuffersize();
diff --git a/src/MusicIO/MusicIO.h b/src/MusicIO/MusicIO.h
index 6c8471d..6119a12 100644
--- a/src/MusicIO/MusicIO.h
+++ b/src/MusicIO/MusicIO.h
@@ -41,6 +41,7 @@ class MusicIO : virtual protected MiscFuncs
void setMidiController(unsigned char ch, unsigned int ctrl, int param);
void setMidiNote(unsigned char chan, unsigned char note);
void setMidiNote(unsigned char chan, unsigned char note, unsigned char
velocity);
+ void setMidiBankAndProgram(unsigned char chan, int bank, unsigned char
prog);

float *zynLeft [NUM_MIDI_PARTS + 1];
float *zynRight [NUM_MIDI_PARTS + 1];
--
1.8.2.2

Other related posts: