Audio Visualizer with multiple midi channels

Hi everyone,

I’m a beginner with Processing and I lack the vocabulary of coding, but I challenged myself to create an audio viewer from .midi files that would give graphic renderings on different levels according to the different channels.
Each channel has a role. As you can see on the mock-up image at the bottom.
For example: Channel 1, rotation of a colour wheel on itself to create a 2D facet ball effect.
Channel 2, rotation of the same colour wheel around the sketch frame.
Channel 3, Generating concentric circles from the centre of the colour wheel…
etc…
I’ve managed to get this result for the moment, by scouring the forums and with the help of a friend, on several Processing windows at the same time. But I’m starting to lose hope. I can’t get several channels to appear. I lack vocabulary, as I said, so maybe I was a bit too ambitious. What do you think? Am I on the right track with this base?
Thanks in advance

import processing.pdf.*;
import java.util.Calendar;
//
import java.util.Collection;
import javax.sound.midi.*;


/* ---------------------------- MIDI & AUDIO ---------------------------- */
AMidiPlayer midiPlayer;

// variables basiques liées aux propriétés audio du fichier midi
public int noteSimple;      // toutes les notes mais sans variation d'octave (DO / DO# / RE …)
public int noteEtOctave;    // toutes les notes reconnues, toutes octaves confondues
public int noteDuree;       // durée de la note dans le temps
public int noteDying;       // chute (fade out ou vide) d'une note une fois jouée
public int noteVelo;        // vélocité (±= force) d'une note
public int chanInst;        // channel midi sollicité (multiples possibles)


/* ---------------------------- GRAPHICS ---------------------------- */
ColorCirlcle colCir;
public int segmentCount = 30;
//boolean savePDF = false;



/**
 *
 */
void setup() {
  size(800, 800);
  colCir = new ColorCirlcle();

  // intanciation du player midi
  midiPlayer = new AMidiPlayer();
  midiPlayer.load(dataPath("gymnop01.mid"));
  midiPlayer.start();
}

/**
 *
 */

int xCircle=0;
int yCircle=0;
int CircleSpeed=5;
//float a=0.0; 

void draw() {
  //
  noStroke();
  colorMode(HSB, 360, width, height);
  background(360);
  //

  for (Note n : midiPlayer.getNotes()) {


    noteEtOctave = int(map(n.note, 0, 127, 50, 100));
    noteSimple   = int(map(n.note % 12, 0, 11, 1, 12));
    noteDying    = int(map(n.dying, 0, 127, 0, 100));
    noteDuree    = int(map(n.living, 0, 127, 180, 360));
    noteVelo     = int(map(n.velocity, 0, 127, 0, 100));
    chanInst     = n.channel;


    println("----------------------------");
    println("note jouée (avec octaves) : "+ noteEtOctave);
    println("note jouée : "+ noteSimple);
    println("durée note jouée : "+ noteDuree);
    println("fin de note jouée : "+ noteDying);
    println("vélocité de note jouée : "+ noteVelo);
    println("channel / track : "+chanInst);
  }
  //
  // MAJ du player midi
  midiPlayer.update();

  //


     pushMatrix();
 // translate(width/2, height/2);
 // translate(-25, -75);
 // rotate(a);  
  rotate(noteVelo);  
  colCir.displayCircle(xCircle, yCircle, 3000);
  popMatrix();

 // a+=.01;
  noteVelo+=.01;

  if ( xCircle >=0 && yCircle<=0) {
      xCircle+=CircleSpeed;
  };
  
  if ( xCircle >=width && yCircle >=0) {
      yCircle+=CircleSpeed;
      xCircle=width;
  };
  
  if ( yCircle >=height && xCircle <=width) {
      yCircle=height;
      xCircle-=CircleSpeed;
  };

  if ( yCircle <=height && xCircle <=0) {
      yCircle-=CircleSpeed;
      xCircle=0;
  };
  // if (savePDF) beginRecord(PDF, timestamp()+".pdf");

  /*
  if (savePDF) {
   savePDF = false;
   endRecord();
   }
   */
}


/**
 *
 */
void keyReleased() {
  /*
  if (key=='s' || key=='S') saveFrame(timestamp()+"_##.png");
   if (key=='p' || key=='P') savePDF = true;
   */
  switch(key) {
  case '1':
    segmentCount = 360;
    break;
  case '2':
    segmentCount = 45;
    break;
  case '3':
    segmentCount = 24;
    break;
  case '4':
    segmentCount = 12;
    break;
  case '5':
    segmentCount = 6;
    break;
  }
}

// timestamp
String timestamp() {
  Calendar now = Calendar.getInstance();
  return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}
import java.util.concurrent.ConcurrentHashMap;

/**
 * C'est ici qu'on 'créé' le sequencer midi
 * MAIS à priori vous n'avez pas spécialement à rentrer dans le détails de cette classe
 * (sauf pour ajouter des méthodes de controles types 'stop'/ 'changer le tempo' etc)
 *
 */
class AMidiPlayer implements Receiver {
  Sequencer sequencer;

  //
  ConcurrentHashMap<Integer, Note> midiData = new ConcurrentHashMap<Integer, Note>();

  public void load(String path) {
    File midiFile = new File(path);
    try {
      sequencer = MidiSystem.getSequencer();
      if (sequencer == null) {
        println("Pas de séquenceur midi");
        exit();
      } else {
        sequencer.open();
        Transmitter transmitter = sequencer.getTransmitter();
        transmitter.setReceiver(this);
        Sequence seq = MidiSystem.getSequence(midiFile);
        sequencer.setSequence(seq);
      }
    }
    catch(Exception e) {
      e.printStackTrace();
      exit();
    }
  }

  public void start() {
    sequencer.start();
  }


  public float getBPM() {
    return sequencer.getTempoInBPM();
  }

  public void update() {
    for (Note n : midiData.values()) {
      if (n.dying > 0) {
        n.dying++;
        if (n.dying > 10) {
          int id = n.channel * 1000 + n.note;
          midiData.remove(id);
        }
      } else {
        n.living++;
      }
    }
  }

  public Collection<Note> getNotes() {
    return midiData.values();
  }

  // j'écris 'send' mais c'est plutot receive (quand un event midi est lu depuis le fichier midi)
  @Override public void send(MidiMessage message, long t) {
    if (message instanceof ShortMessage) {
      ShortMessage sm = (ShortMessage) message;
      int cmd = sm.getCommand();
      if (cmd == ShortMessage.NOTE_ON || cmd == ShortMessage.NOTE_OFF) {
        int channel = sm.getChannel() - 1;
        int note = sm.getData1();
        int velocity = sm.getData2();
        int id = channel * 1000 + note;
        if (cmd == ShortMessage.NOTE_ON && velocity > 0) {
          midiData.put(id, new Note(channel, note, velocity));
        } else {
          midiData.get(id).dying++;
        }
      }
    }
  }

  @Override public void close() {
  }
}
class ColorCirlcle {

  ColorCirlcle() {
    //
  }

  /**
   *  
   */
  void displayCircle(float _xPos, float _yPos, int _radius) {
    //
    
    //
    float angleStep = 360/segmentCount;
    beginShape(TRIANGLE_FAN);
    //
    vertex(_xPos, _yPos);
    //
    //
    for (float angle=0; angle<=360; angle+=angleStep) {
      //
      float vx = _xPos + cos(radians(angle))*_radius;
      float vy =  _yPos + sin(radians(angle))*_radius;
      vertex(vx, vy);
      // Hue / Saturation / Brightness
      fill(angle, noteDuree, 600);
    }
    endShape();
  }
  //
}
class Note {
  int channel;
  int note;
  int velocity;
  int living;
  int dying;
  Note(int c, int n, int v) {
    channel = c;
    note = n;
    velocity = v;
  }
  void update() {
  }
}

1 Like