accordeon

🪗 Play the accordion with your computer keyboard

Synthesia alternative in Processing

Screenshot 2023-05-18 at 10 31 12

import javax.sound.midi.*;
import java.io.*;
import ddf.minim.*;
import ddf.minim.ugens.*;
import javax.sound.midi.*;
Minim minim;
AudioOutput out;
Synthesizer synth;
MidiChannel[] channels;
int CH0=0, CH1=1;
int BEGIN_Y=400;
int yStep=3; //hmm... lin relation to 60fps?
int curTime;
int yBar;
boolean isPlaying = true;
PShape square, alien;
int y=BEGIN_Y; //-12275;
float scaler=1/50.;
int timeShift = 3560; //1280;
int minNote=40;
int maxNote=80;
int numNotes=maxNote-minNote;
int noteWidth=50;
String fileName = "La Noyee - bewerkt met Reaper midi_export.MID";
ArrayList<Note> notes = new ArrayList();
boolean muteTrack0 = false;
boolean muteTrack1 = false;
int tempo = 85;
float tempoFactor = 1;

void setup() {
  //size(1200, 800);
  fullScreen(P3D);
  alien = createShape(GROUP);
  textFont(loadFont("AlBayan-Bold-20.vlw"));
  textAlign(CENTER, CENTER);

  minim = new Minim(this);
  out   = minim.getLineOut();

  yBar = int(height/1.5);

  // try to get the default synthesizer from JavaSound
  // if it fails, we print a message to the console
  // and don't do any of the sequencing.
  try
  {
    synth = MidiSystem.getSynthesizer();
    synth.open();
    // get all the channels for the synth
    channels = synth.getChannels();
    // we're only going to use two channels,
    // which we'll now configure to use particular midi instruments.
    // but you should have up to 16 channels available.
    // for a list of general midi instrument program numbers, see: http://www.midi.org/techspecs/gm1sound.php
    channels[CH0].programChange( 21 ); // 5 should be "electric piano 1"
    channels[CH1].programChange( 23) ;//21 ); // 33 should be "acoustic bass"

    // ok make a sequence with our output and custom Instrument
    out.setTempo( tempo );
    //out.pauseNotes();

    //// remember that time and duration are expressed relative to the tempo.
    //// so the first two arguments here mean: on beat 0, play a dotted quarter note.
    //note( 0, 1.5, CH1, "D4", 0.8 );
    ////....
    //out.resumeNotes();
  }
  catch( MidiUnavailableException ex )
  {
    // oops there wasn't one.
    println( "No default synthesizer, sorry bud." );
  }

  String[] lines = loadStrings("lines.txt");
  for (String line : lines) {
    notes.add(new Note(line));
  }

  for (Note n : notes) {
    //int x = int(n.octave*noteWidth); 
    int x = int(map(n.noteNumber, minNote, maxNote, 0, width-200));
    int y = -int(n.timestamp*scaler);
    int w = (n.name.indexOf("#")>-1) ? noteWidth/2 : noteWidth;
    float darkness = (n.name.indexOf("#")>-1) ? .6 : 1;
    int h = -int(n.duration*scaler*tempoFactor);

    square = createShape(RECT, x, y, w, h, 10);
    square.setFill(n.track==1 ?
      color(142*darkness, 169*darkness, 204*darkness) :
      color(176*darkness, 227*darkness, 111*darkness));
    square.setStroke(false);
    alien.addChild(square);
  }
}

ArrayList<Note> getNotesByTimestamp(int t, int range) {
  ArrayList<Note> result = new ArrayList();
  for (Note n : notes) {
    if (n.timestamp-t>-range && n.timestamp-t<range) {
      result.add(n);
    }
  }
  return result;
}

void draw() {
  background(40);
  shape(alien, 0, y);

  fill(255);
  for (Note n : notes) {
    //float x = n.octave*noteWidth; 
    float x = map(n.noteNumber, minNote, maxNote, 0, width-200);
    float yy = -n.timestamp*scaler;
    int w = (n.name.indexOf("#")>-1) ? 25 : 50;
    int h = -int(n.duration*scaler);
    text(n.name, x, yy+y, w, h);
  }

  stroke(255);
  strokeWeight(2);
  line(0, height/1.5, width, yBar);
  noStroke();
  fill(0, 200);
  rect(0, height/1.5, width, height-yBar);

  curTime = int((y-yBar)/scaler) + timeShift;

  int detectNoteTimeWindow = int(100*tempoFactor);
  ArrayList<Note> notes = getNotesByTimestamp(curTime, detectNoteTimeWindow); //(abs(curTime-timeStamp)<50
  if (notes.size()>0) {
    //println(curTime, notes);
    for (Note n : notes) {
      int octave = ( n.noteNumber / 12 ) - 1;
      String name = n.name;
      if (isPlaying && (n.track==1 && !muteTrack0) || (n.track==2 && !muteTrack1)) {
        note( 0, n.duration/10000.*tempoFactor, n.track-1, name+octave, 1) ;// );
      }
      //
      //println(n.duration);
    }
  }

  //if (y%20==0) {
  //  note( 0, .1, CH1, "D4", 0.8 );
  //}

  if (isPlaying) {
    y+=(yStep * tempoFactor);
  }

  stroke(0);
  fill(255);
  for (int x=0; x<=width; x+=noteWidth) {
    rect(x, height-100, x+noteWidth, 100);
  }
}

void mouseWheel(MouseEvent event) {
  y -= event.getCount()*3; // *.00001;
  //println(y);
}

void keyPressed() {
  if (key==BACKSPACE) y=BEGIN_Y;
  if (key=='+') yStep++;
  if (key=='-') yStep--;
  if (key=='[') tempoFactor-=.1;
  if (key==']') tempoFactor+=.1;
  if (key=='1') muteTrack0=!muteTrack0;
  if (key=='2') muteTrack1=!muteTrack1;
  if (keyCode==LEFT) y-=250;
  //if (keyCode==LEFT) yStep-=50;
  //println(timeShift);

  if (key==' ') isPlaying=!isPlaying;
}

class Note {
  int track, noteNumber, octave, velocity, timestamp, duration;
  String name;

  Note(String s) {
    String[] items = s.split(",");
    track = int(items[0]);
    timestamp = int(items[1]);
    duration = int(items[2]); //in ms?
    noteNumber = int(items[3]);
    name = items[4];
    octave = int(items[5]);
  }

  String toString() {
    return name;
  }
}

class MidiSynth implements ddf.minim.ugens.Instrument
{
  int         channel;
  int         noteNumber;
  int         noteVelocity;

  MidiSynth( int channelIndex, String noteName, float vel )
  {
    channel = channelIndex;
    // to make our sequence code more readable, we use note names.
    // and then convert the note name to a Midi note value here.
    noteNumber = (int)Frequency.ofPitch(noteName).asMidiNote();
    // similarly, we specify velocity as a [0,1] volume and convert to [1,127] here.
    noteVelocity = 1 + int(126*vel); 
  }

  void noteOn(float duration) {
    channels[channel].noteOn( noteNumber, noteVelocity );
  }

  void noteOff() {
    channels[channel].noteOff(noteNumber);
  }
}

void note( float time, float duration, int channelIndex, String noteName, float velocity ) {
  out.playNote( time, duration, new MidiSynth( channelIndex, noteName, velocity ) );
}