kramann.info
© Guido Kramann

Login: Passwort:










kramann.info
© Guido Kramann

Login: Passwort:




Vorversuche zur Ermittlung sinnvoller Parameter und Anspieltechniken

(EN google-translate)

(PL google-translate)

Zusammenfassung der Ergebnisse
  • Unterschiede in der Anschlagsstärke sind in 10er-Schritten bemerkbar.
  • Midi-Lautstärke 80 ist bereits extrem laut.
  • Sinnvolle Stufen vorläufig: 10 20 30 40 50 60 70 80 (ppp pp p mp mf f ff fff)
  • Man hat hier quasi eine/n Pianistin/en mit beliebig vielen Fingern. Fazit: Das Wegnehmen eines Fingers von einer Taste (noteOff) kann völlig frei gewählt werden. Das erleichtert polyphones Spiel erheblich (Halten eines Tones einer Stimme, bis ein Ton der gleichen Stimme erscheint).
  • Jeder noch so leise Ton wird angeschlagen, hat aber eine umso größere Latenz, je leiser er ist.
  • Womöglich kann diese Latenz über die 500ms Latenzzeit bis zu einer Lautstärke OBERHALB von 40 ausgeglichen werden.
  • Werden Töne unterschiedlicher Lautstärke angespielt, die leiser als 40 sind, klappern sie i.d.R.
  • Womöglich kann das durch größere Latenz bei den Einstellungen an der Kontrolleinheit des Disklaviers ausgeglichen werden (?)
  • SUSTAIN -- Entdämpfen: Kanal 2, Befehl 64, Wert 127, Dämpfen: Kanal 2, Befehl 64, Wert 0
  • Unacorda -- Soft, Hämmer näher an Saiten geklappt: Kanal 2, Befehl 67, Wert 127, härter: Kanal 2, Befehl 67, Wert 0
  • Sostenuto -- keine erkennbare Reaktion, Befehl 66.

Alle mechanischen Aktionen benötigen Zeit. Dies muss bei der Entwicklung der Ansteuerung berücksichtigt werden.


  • Die Grundkonfiguration ist die, dass ein Linux (Xubuntu 20.04) Laptop mit dem Disklavier verbunden ist.
  • Zusätzlich wird ein Midi-Twister am Laptop angeschlossen (16 Drehregler für Parameter über Midi eingebunden).
  • Die Reihenfolge beim Anschließen der Midi-Devices muss am Laptop immer gleich bleiben, damit die Kanalzuordnungen immer gleich sind.

Vorbereiten des Laptops

  1. Hochfahren
  2. Erzeugen von vier virtuellen Midi-Devices im Terminal: sudo modprobe snd-virmidi
  3. Anstecken des USB-Midi-Umsetzers vom Disklavier her.
  4. Anstecken des USB-Kabels vom Midi-Twister her.
  5. Test, ob alles da ist im Terminal: aconnect -o
  6. Terminal, an das Klavier senden auf MIDI 3-2: aconnect 30:0 32:0
  7. Terminal, vom Klavier empfangen auf MIDI 3-1: aconnect 32:0 29:0
  8. Terminal, an den Twister senden auf MIDI 3-3: aconnect 31:0 36:0
  9. Terminal, vom Twister empfangen auf MIDI 3-3: aconnect 36:0 31:0
client 14: 'Midi Through' [type=Kernel]
    0 'Midi Through Port-0'
client 28: 'Virtual Raw MIDI 3-0' [type=Kernel,card=3]
    0 'VirMIDI 3-0     '
client 29: 'Virtual Raw MIDI 3-1' [type=Kernel,card=3] zukünftig Senden an das Klavier
    0 'VirMIDI 3-1     '
client 30: 'Virtual Raw MIDI 3-2' [type=Kernel,card=3] zukünftig Empfangen von Klavier
    0 'VirMIDI 3-2     '
client 31: 'Virtual Raw MIDI 3-3' [type=Kernel,card=3] zukünftig TWISTER
    0 'VirMIDI 3-3     '
client 32: 'USB MIDI Interface' [type=Kernel,card=4]
    0 'USB MIDI Interface MIDI 1'
client 36: 'Midi Fighter Twister' [type=Kernel,card=5]
    0 'Midi Fighter Twister MIDI 1'

Code 0-1: Ausgabe von aconnect-o

Aufbau Laptop-seitig.

Bild 0-1: Aufbau Laptop-seitig.

  • Alle Projekte arbeiten mit Java/Processing 3.6.4 (vergleiche processing.org)
  • Die Zusatzlibrary "themidibus" muß nachinstalliert werden.
  • Die Zusatzlibrary "ComposingForEveryone" kann nachinstalliert werden, oder entsprechende Einträge sollten entfernt werden.
Versuch #1 -- Vom Laptop repetitiv einen Ton an das Klavier senden

Einfacher Funktionstest zur Überprüfung der Datenleitungen.

  • c1 wird jede zweite Sekunde angeschlagen
  • Wenn Töne von Hand gespielt werden, sollten die vom Laptop empfangenen Daten im Terminal erscheinen.
import themidibus.*;  //contributed library themidibus needed!
MidiBus[] bus;
public void setup()
{
    bus = new MidiBus[4];
    bus[0] = new MidiBus(this, -1, "VirMIDI [hw:3,0,0]"); //später ev. Moddart Klavier U1
    bus[1] = new MidiBus(this, "VirMIDI [hw:3,1,0]", -1); //VOM Disklavier empfangen
    bus[2] = new MidiBus(this, -1, "VirMIDI [hw:3,2,0]"); //AN das DISKLAVIER senden
    bus[3] = new MidiBus(this, "VirMIDI [hw:3,3,0]", "VirMIDI [hw:3,3,0]"); //MIDITWISTER       

    frameRate(1);
}

int index=0;
public void draw()
{
  int u = index%2;
  if(PLAYING)
  {
    if(index%2==0)
    {
         //Kanal Höhe Lautstärke
         bus[2].sendNoteOn(0,60,40);
    }
    else
    {
         bus[2].sendNoteOff(0,60,40);
    }
  }
  index++;
}

void noteOn(int channel, int pitch, int velocity) 
{
  // Receive a noteOn
  println();
  println("Note On:");
  println("--------");
  println("Channel:"+channel);
  println("Pitch:"+pitch);
  println("Velocity:"+velocity);
}

void noteOff(int channel, int pitch, int velocity) 
{
  // Receive a noteOff
  println();
  println("Note Off:");
  println("--------");
  println("Channel:"+channel);
  println("Pitch:"+pitch);
  println("Velocity:"+velocity);
}

void controllerChange(int channel, int number, int value) 
{
  // Receive a controllerChange
  println();
  println("Controller Change:");
  println("--------");
  println("Channel:"+channel);
  println("Number:"+number);
  println("Value:"+value);
}

Code 0-2: Quelltext des Processing-Sketches Disklavier001

Versuch #2 -- Disklavier spielt vermindertes Arpeggio über einem angeschlagenen Ton
Disklavier spielt Arpeggien über einem einzelnen angeschlagenen Ton.

Bild 0-2: Disklavier spielt Arpeggien über einem einzelnen angeschlagenen Ton.

Video auf Youtube: Disklavier spielt Arpeggien über einem einzelnen angeschlagenen Ton.
import themidibus.*;  //contributed library themidibus needed!
import processchains.simple.SimplePiano;

boolean PLAYING = false;

SimplePiano piano;

MidiBus[] bus;

/*
.. am Twister muss Start- und Endlautstärke eingestellt werden.
*/

int LAUT1 = 0;
int LAUT2 = 0;
int[] laut = new int[19];
public void setup()
{
    piano = new SimplePiano(this);     // piano sound
  
    bus = new MidiBus[4];
    bus[0] = new MidiBus(this, -1, "VirMIDI [hw:3,0,0]"); //Moddart Klavier U1
    bus[1] = new MidiBus(this, "VirMIDI [hw:3,1,0]", -1); //VOM Disklavier empfangen
    bus[2] = new MidiBus(this, -1, "VirMIDI [hw:3,2,0]"); //AN das DISKLAVIER senden
    bus[3] = new MidiBus(this, "VirMIDI [hw:3,3,0]", "VirMIDI [hw:3,3,0]"); //MIDITWISTER       
    size(200,200);
    frameRate(20);
}


int index=0;
int GRUNDTON = 48;
int GRUNDTON_NEU = 48;
public void draw()
{
  background(0);
  text(LAUT1+" "+LAUT2,20,20);
  
  int u = index%20;
  if(PLAYING)
  {
    if(u==19)
    {
        for(int m=1;m<20;m++)
        {
            int midi = GRUNDTON+m*3;
            bus[2].sendNoteOff(0,midi,laut[m-1]);
        }
            PLAYING=false;
    }
    else
    {
            int laute = LAUT1 + (int)((float)(LAUT2 - LAUT1)/(float)19.0f);
            laut[u]=laute;
            int midi = GRUNDTON+u*3;
            bus[2].sendNoteOn(0,midi,laute);
    }
  }
    index++;
}

public void keyPressed()
{
    if(key==' ')
    {
        PLAYING=false;
        for(int m=21;m<=108;m++)
           bus[2].sendNoteOff(0,m,80);
    }
    else if(key=='r')
    {
        for(int m=21;m<=108;m++)
           bus[2].sendNoteOff(0,m,80);
        index=0;   
        PLAYING=true;
                    
    }
}


void noteOn(int channel, int pitch, int velocity) {
  // Receive a noteOn
  println();
  println("Note On:");
  println("--------");
  println("Channel:"+channel);
  println("Pitch:"+pitch);
  println("Velocity:"+velocity);
  
  
  //piano.play(pitch);
  GRUNDTON=pitch;
  index=0;
  PLAYING=true;
  //GRUNDTON_NEU = GRUNDTON; //weg, wenn erst komplett sein soll.
}

void noteOff(int channel, int pitch, int velocity) {
  // Receive a noteOff
  println();
  println("Note Off:");
  println("--------");
  println("Channel:"+channel);
  println("Pitch:"+pitch);
  println("Velocity:"+velocity);
}

void controllerChange(int channel, int number, int value) {
  // Receive a controllerChange
  println();
  println("Controller Change:");
  println("--------");
  println("Channel:"+channel);
  println("Number:"+number);
  println("Value:"+value);
  
  
  if(channel==0 && number==0) LAUT1 = value; 
  if(channel==0 && number==1) LAUT2 = value; 
}

Code 0-3: Disklavier002

Disklavier002.zip -- Processingsketch
Versuch #3 -- Sext-Akkord-Parallelbewegungen
Sketch: Disklavier003.zip
Youtube-Video Sext-Akkord-Parallelbewegungen
Sext-Akkord-Parallelbewegungen

Bild 0-3: Sext-Akkord-Parallelbewegungen

Versuch #4 -- Pedale
Sketch Disklavier004_PEDALE.zip
Sustain-Pedal in Kombination mit verschiedenen Anschlagstärken
  • Sustain kann nach dem Datenblatt nur an (127), oder aus (0) sein.
  • Leise angeschlagene Töne in den unteren Registern sprechen nicht an, wenn das Klavier entdämpft ist.
  • Das könnte daran liegen, dass das Tempo des Arpeggios schneller ist, als ein leister Tonanschlag an Zeit benötigt.

Nachfolgend wird das Softpedal verwendet. Wenn die Hämmer nahe an die Saiten gebracht werden, wird der Klang weicher.

Verwendung des Softpedals.
Versuch #5 -- Anschlag
Sketch Disklavier005_Anschlag.zip

Bei diesem Versuch wird untersucht, bis zu welcher Anaschlagstärke ein Ton noch anspricht. Dabei kommt heraus, dass bei genügend langsamem Tempo auch die allerleisesten durchkommen. Das Problem ist nicht das Ansprechen, sondern wie lange es bei einem leisen Ton dauert, ihn anzuspielen:

Versuch #6 -- Aufzeichnen

Nachfolgend wurde aufgezeichnet und in vierfachem Tempo abgespielt:

Aufgezeichnete Phrase in vierfachem Tempo abspielen -- https://youtu.be/v8g6D18jia8

Interessanter Effekt: Bei einer sehr schnellen Tonrepetition mit schwachem Anschlag, erklingt nur sporadisch ein Ton:


  • Umgesetzt wurde das mit folgendem Processing-Sketch, bei dem damit begonnen wurde, dem Disklavier eine Klasse gleichen Namens zuzuordnen.
  • Per Computertasten können hier Modi ausgewählt werden.
  • Aufgezeichnetes wird bei Betätigen des Dämpfungspedals abgespielt.
Disklavier009_rec.zip
public class Disklavier implements Runnable
{
                                                    //    0         1         2     3    4
     ArrayList<int[]> rec = new ArrayList<int[]>(); // t-Tsrat, 1=on 0=off, midi, laute  Durchgang
     int DAUER_MILLIS=0;
     int[][] arr;
     int durchgang = 0;
     int TEILUNG=4;
     
     long Tstart = 0; //Zeit beim ersten angeschlagenen Ton
  
     int INDEX = 0;
     MidiBus[] bus;
     int MODE = 0;
     int MODEalt = 0;
     int[] onetime = new int[100];
     long tickmillis = 1000;
     public Disklavier()
     {
         initMidi();
         (new Thread(this)).start();
     }

     public void init()
     {
          INDEX = 0;
          allNotesOff();
          rec.clear();
     }
     
     public void setMode(int MODE)
     {
         this.MODE = MODE;
     }
     
     public void allNotesOff()
     {
          for(int m=21;m<=108;m++)
          {
             bus[2].sendNoteOff(0,m,40);
          }
     }
     
     public void initMidi()
     {
       bus = new MidiBus[4];
       bus[0] = new MidiBus(this, -1, "VirMIDI [hw:3,0,0]"); //Moddart Klavier U1
       bus[1] = new MidiBus(this, "VirMIDI [hw:3,1,0]", -1); //VOM Disklavier empfangen
       bus[2] = new MidiBus(this, -1, "VirMIDI [hw:3,2,0]"); //AN das DISKLAVIER senden
       bus[3] = new MidiBus(this, "VirMIDI [hw:3,3,0]", "VirMIDI [hw:3,3,0]"); //MIDITWISTER              
     }
     
     public void setTickMillis(int tickmillis)
     {
         this.tickmillis = (long)tickmillis;
     }
     
     int[] diaton = {0,2,4,5,7,9,11};
     public void selbsttest()
     {
         if(tickmillis!=500)
           setTickMillis(500);
         int inx = INDEX%15;
         int okt = inx/diaton.length;
         int dia = diaton[inx%diaton.length];
         int mid = okt*12 + dia + 60;
         if(INDEX%2==0)
         {
         bus[2].sendNoteOff(0,mid,70);  
         bus[2].sendNoteOn(0,mid,70);
         }
         else
         {
         bus[2].sendNoteOff(0,mid,1);  
         bus[2].sendNoteOn(0,mid,1);
         }
     }
     
     public void run()
     {
         long t = System.currentTimeMillis(); 
         while(true)
         {
              if(MODE!=MODEalt)
              {
                   MODEalt = MODE;
                   zeros(onetime);
              }
              switch(MODE)
              {
                  case 0:
                      if(onetime[0]==0)
                      {
                           init();
                           onetime[0]=1;
                      }
                  break;
                  case 1: //Selbsttest
                      if(onetime[1]==0)
                      {
                           setTickMillis(500);
                           onetime[1]=1;
                      }
                      selbsttest();
                  break;
                  case 2:
                    //siehe noteOn
                  break;
                  case 3:
                    abspielen();
                  break;
              }
          
              if(MODE!=3)
              {
                while(System.currentTimeMillis()<t+tickmillis);
                t = System.currentTimeMillis(); 
                INDEX++;
              }
         }
     }
     
     public void abspielen()
     {
         int t = (int)(System.currentTimeMillis() - Tstart);
         int tz = t%DAUER_MILLIS;
         int durchgang = t/DAUER_MILLIS;
         for(int i=0;i<arr.length;i++)
         {
               if(arr[i][0]<tz && arr[i][4]<=durchgang && arr[i][2]>=21 && arr[i][2]<=108)
               {
                    if(arr[i][1]==1)
                       bus[2].sendNoteOn(0,arr[i][2],arr[i][3]);
                    else if(arr[i][1]==0)
                       bus[2].sendNoteOff(0,arr[i][2],arr[i][3]);
                    arr[i][4]++;   
               }
         }
     }
     
     void noteOn(int channel, int pitch, int velocity) 
     {
       // Receive a noteOn
       println();
       println("Note On:");
       println("--------");
       println("Channel:"+channel);
       println("Pitch:"+pitch);
       println("Velocity:"+velocity);
  
      //GRUNDTON_NEU = GRUNDTON; //weg, wenn erst komplett sein soll.
       if(MODE==2)
       {
           if(rec.size()==0)
           {
              Tstart = System.currentTimeMillis();
           }
           
           rec.add(new int[] {(int)(System.currentTimeMillis()-Tstart)/TEILUNG, // t-tStart
                             1, // 1==on 0==off
                             pitch,
                             velocity,
                             0 //Zähler für später
                           });
       }
     }

     void noteOff(int channel, int pitch, int velocity) 
     {
         // Receive a noteOff
         println();
         println("Note Off:");
         println("--------");
         println("Channel:"+channel);
         println("Pitch:"+pitch);
         println("Velocity:"+velocity);
       if(MODE==2)
       {
           if(rec.size()==0)
              Tstart = System.currentTimeMillis();
           rec.add(new int[] {(int)(System.currentTimeMillis()-Tstart)/TEILUNG, // t-tStart
                               0, // 1==on 0==off
                               pitch,
                               velocity,
                               0 //Zähler für später!
                             });   
       }
     }

     void controllerChange(int channel, int number, int value) 
     {
         // Receive a controllerChange
         println();
         println("Controller Change:");
         println("--------");
         println("Channel:"+channel);
         println("Number:"+number);
         println("Value:"+value); 
           if(MODE==2 && rec.size()>0 && number==67) //Linkes Pedal (Dämpfung) zum Starten des Abspielens
           {
               rec.add(new int[] {(int)(System.currentTimeMillis()-Tstart)/TEILUNG, // t-tStart
                               0, // 1==on 0==off
                               0,
                               0,
                               0 //Zähler für später!
                             });   
               DAUER_MILLIS = rec.get(rec.size()-1)[0] - rec.get(0)[0]; 
               Tstart = System.currentTimeMillis(); //neue Bedeutung: Startzeit beim zyklischen Abspielen
               arr = new int[rec.size()][];
               for(int i=0;i<arr.length;i++)
                  arr[i] = rec.get(i);
               durchgang = 0;   
               MODE=3;
           }                                    
  
     }     
}

Code 0-4: Klasse Disklavier