├── Note.pde ├── LICENSE ├── midi_visualizer_framework.pde ├── example ├── Note.pde ├── example.pde └── NoteManager.pde ├── NoteManager.pde └── README.md /Note.pde: -------------------------------------------------------------------------------- 1 | /* 2 | Authored by Thomas Castleman, 2018 3 | */ 4 | 5 | // Note class handles attributes that all played notes share 6 | class Note { 7 | 8 | int channel, velocity, pitch; // store the channel, velocity and pitch 9 | int lifespan; // lifespan of note, in frames 10 | boolean isReleased; // whether or not the note has been released yet 11 | 12 | // constructor for new Note object 13 | Note(int channel_, int pitch_, int velocity_) { 14 | this.channel = channel_; 15 | this.pitch = pitch_; 16 | this.velocity = velocity_; 17 | this.lifespan = 5; 18 | this.isReleased = false; 19 | } 20 | 21 | // update note properties 22 | void update() { 23 | 24 | } 25 | 26 | // display note on canvas 27 | void display() { 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Thomas Castleman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /midi_visualizer_framework.pde: -------------------------------------------------------------------------------- 1 | import themidibus.*; // import the midibus library 2 | import java.util.*; // import java util to use ArrayLists 3 | 4 | MidiBus bus; // interface with the MidiBus library 5 | NoteManager nm; // note manager to handle tracking of MIDI notes 6 | 7 | void setup() { 8 | size(700, 700); // set the size of the window 9 | background(0); // set the background color of window 10 | MidiBus.list(); // list all available Midi devices 11 | bus = new MidiBus(this, 0, 1); // tell MidiBus to listen to connected MIDI instrument 12 | nm = new NoteManager(); // create a note manager 13 | } 14 | 15 | void draw() { 16 | background(0); // reset the background color 17 | nm.track(); // track notes being played, updating and displaying them 18 | } 19 | 20 | // when a new key is pressed on the MIDI instrument 21 | void noteOn(int channel, int pitch, int velocity) { 22 | // add this note 23 | nm.addNote(new Note(channel, pitch, velocity)); 24 | } 25 | 26 | // when a key is released on the MIDI instrument 27 | void noteOff(int channel, int pitch, int velocity) { 28 | // release this note 29 | nm.releaseNote(new Note(channel, pitch, velocity)); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /example/Note.pde: -------------------------------------------------------------------------------- 1 | 2 | // Note class handles attributes that all played notes share 3 | class Note { 4 | 5 | int channel, velocity, pitch; // store the channel, velocity and pitch 6 | int lifespan; // lifespan of note, in frames 7 | boolean isReleased; // whether or not the note has been released yet 8 | 9 | PVector position; // note's spatial position 10 | PVector positionalVelocity; // note's velocity in space 11 | 12 | // constructor for new Note object 13 | Note(int channel_, int pitch_, int velocity_) { 14 | this.channel = channel_; 15 | this.pitch = pitch_; 16 | this.velocity = velocity_; 17 | this.lifespan = 45; // arbitrary lifespan of 45 frames 18 | this.isReleased = false; 19 | 20 | // calculate initial position by scaling pitch to an x value vertically centered on screen 21 | this.position = new PVector(scaleVal(this.pitch, MINPITCH, MAXPITCH, 0, width), height / 2); 22 | 23 | // generate positional velocity loosely based on note velocity 24 | this.positionalVelocity = new PVector(random(-this.velocity, this.velocity), random(-this.velocity, this.velocity)); 25 | this.positionalVelocity.mult(0.1); 26 | } 27 | 28 | // update note properties 29 | void update() { 30 | // change the note's position on the screen by its positional velocity 31 | this.position.add(this.positionalVelocity); 32 | } 33 | 34 | // display note on canvas 35 | void display() { 36 | fill(scalePitchToRainbow(this.pitch), scaleVal(this.lifespan, 0, 45, 0, 255)); // fill color based on pitch, alpha based on lifespan left (fading) 37 | ellipse(this.position.x, this.position.y, 60, 60); // ellipse representation 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /example/example.pde: -------------------------------------------------------------------------------- 1 | import themidibus.*; // import the midibus library 2 | import java.util.*; // import java util to use ArrayLists 3 | 4 | MidiBus bus; // interface with the MidiBus library 5 | NoteManager nm; // note manager to handle tracking of MIDI notes 6 | 7 | int MINPITCH = 21; // lowest possible pitch value on this MIDI instrument 8 | int MAXPITCH = 108; // highest possible pitch value 9 | 10 | void setup() { 11 | fullScreen(); // set the size of the window 12 | background(0); // set the background color of window 13 | MidiBus.list(); // list all available Midi devices 14 | bus = new MidiBus(this, 1, 4); // tell MidiBus to listen to connected MIDI instrument 15 | nm = new NoteManager(); // create a note manager 16 | } 17 | 18 | void draw() { 19 | background(0); // reset the background color 20 | nm.track(); // track notes being played, updating and displaying them 21 | } 22 | 23 | // when a new key is pressed on the MIDI instrument 24 | void noteOn(int channel, int pitch, int velocity) { 25 | // add this note 26 | nm.addNote(new Note(channel, pitch, velocity)); 27 | } 28 | 29 | // when a key is released on the MIDI instrument 30 | void noteOff(int channel, int pitch, int velocity) { 31 | // release this note 32 | nm.releaseNote(new Note(channel, pitch, velocity)); 33 | } 34 | 35 | // scale value from one range to another 36 | float scaleVal(float value, int oldMin, int oldMax, int newMin, int newMax) { 37 | return (((float) (value - oldMin)) / (oldMax - oldMin)) * (newMax - newMin) + newMin; 38 | } 39 | 40 | // scale pitch to a rainbow color value 41 | color scalePitchToRainbow(int pitch) { 42 | float freq = 2 * (2 * (float) Math.PI) / (MAXPITCH - MINPITCH); 43 | 44 | // scale between 0 and pitchRange 45 | int truePitch = pitch - MINPITCH; 46 | 47 | // calc rgb values using out of phase waves 48 | float r = scaleVal((float) Math.cos(truePitch * freq), -1, 1, 0, 255); 49 | float b = scaleVal((float) Math.cos(truePitch * freq + 2), -1, 1, 0, 255); 50 | float g = scaleVal((float) Math.cos(truePitch * freq + 4), -1, 1, 0, 255); 51 | 52 | return color(r, g, b); 53 | } 54 | -------------------------------------------------------------------------------- /NoteManager.pde: -------------------------------------------------------------------------------- 1 | 2 | // NoteManager class manages the tracking of MIDI notes, as played live 3 | class NoteManager { 4 | 5 | public HashSet notes = new HashSet(); // all note objects currently being tracked 6 | private HashSet notesToAdd = new HashSet(); // notes to add to the list of tracked notes 7 | private HashSet release = new HashSet(); // notes that will be released on this iteration of the draw() loop 8 | private HashSet notesToRelease = new HashSet(); // notes waiting for the next iteration of draw() to be released 9 | 10 | // construct a new NoteManager object 11 | public NoteManager() { 12 | 13 | } 14 | 15 | // add a new note to tracked notes 16 | void addNote(Note n) { 17 | // add note to list of notes that will be tracked in the next iteration of draw() 18 | notesToAdd.add(n); 19 | } 20 | 21 | // remove a note from tracked notes 22 | void releaseNote(Note n) { 23 | // add note to list of notes that will be released in the next iteration of draw() 24 | notesToRelease.add(n); 25 | } 26 | 27 | // add new notes to tracked notens, remove old notes from tracked notes 28 | void track() { 29 | this.release.addAll(this.notesToRelease); // add every note waiting to be released to list of notes about to be released 30 | this.notesToRelease.clear(); // remove everything from list of notes waiting to be released 31 | 32 | // for each note that needs to be released 33 | for (Note n : this.release) { 34 | // find its counterpart in the tracked notes array 35 | for (Note m : this.notes) { 36 | if (n.channel == m.channel && n.pitch == m.pitch) { 37 | m.isReleased = true; // record that this note is now released 38 | } 39 | } 40 | } 41 | 42 | this.release.clear(); // remove everything from the list of notes to remove 43 | 44 | this.notes.addAll(this.notesToAdd); // add every note waiting to be kept track of 45 | this.notesToAdd.clear(); // remove everything from list of notes waiting to be tracked 46 | 47 | // iterate through all notes currently being tracked 48 | Iterator iter = this.notes.iterator(); 49 | while (iter.hasNext()) { 50 | Note n = iter.next(); 51 | 52 | // if note has been released, decrement lifespan 53 | if (n.isReleased) { 54 | n.lifespan--; 55 | } 56 | 57 | // update and display each note 58 | n.update(); 59 | n.display(); 60 | 61 | // if a note is finished, remove from tracked notes 62 | if (n.lifespan <= 0) { 63 | iter.remove(); 64 | } 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /example/NoteManager.pde: -------------------------------------------------------------------------------- 1 | 2 | // NoteManager class manages the tracking of MIDI notes, as played live 3 | class NoteManager { 4 | 5 | public HashSet notes = new HashSet(); // all note objects currently being tracked 6 | private HashSet notesToAdd = new HashSet(); // notes to add to the list of tracked notes 7 | private HashSet release = new HashSet(); // notes that will be released on this iteration of the draw() loop 8 | private HashSet notesToRelease = new HashSet(); // notes waiting for the next iteration of draw() to be released 9 | 10 | // construct a new NoteManager object 11 | public NoteManager() { 12 | 13 | } 14 | 15 | // add a new note to tracked notes 16 | void addNote(Note n) { 17 | // add note to list of notes that will be tracked in the next iteration of draw() 18 | notesToAdd.add(n); 19 | } 20 | 21 | // remove a note from tracked notes 22 | void releaseNote(Note n) { 23 | // add note to list of notes that will be released in the next iteration of draw() 24 | notesToRelease.add(n); 25 | } 26 | 27 | // add new notes to tracked notens, remove old notes from tracked notes 28 | void track() { 29 | this.release.addAll(this.notesToRelease); // add every note waiting to be released to list of notes about to be released 30 | this.notesToRelease.clear(); // remove everything from list of notes waiting to be released 31 | 32 | // for each note that needs to be released 33 | for (Note n : this.release) { 34 | // find its counterpart in the tracked notes array 35 | for (Note m : this.notes) { 36 | if (n.channel == m.channel && n.pitch == m.pitch) { 37 | m.isReleased = true; // record that this note is now released 38 | } 39 | } 40 | } 41 | 42 | this.release.clear(); // remove everything from the list of notes to remove 43 | 44 | this.notes.addAll(this.notesToAdd); // add every note waiting to be kept track of 45 | this.notesToAdd.clear(); // remove everything from list of notes waiting to be tracked 46 | 47 | // iterate through all notes currently being tracked 48 | Iterator iter = this.notes.iterator(); 49 | while (iter.hasNext()) { 50 | Note n = iter.next(); 51 | 52 | // if note has been released, decrement lifespan 53 | if (n.isReleased) { 54 | n.lifespan--; 55 | } 56 | 57 | // update and display each note 58 | n.update(); 59 | n.display(); 60 | 61 | // if a note is finished, remove from tracked notes 62 | if (n.lifespan <= 0) { 63 | iter.remove(); 64 | } 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # midi-visualizer-framework 2 | 3 | A generalized framework that can be used to easily create visualizations of MIDI input using [Processing](https://processing.org/) and the [Midibus library](https://github.com/sparks/themidibus). 4 | 5 | ## Purpose 6 | 7 | When using input from a MIDI instrument in Processing, issues may arise with the handling of new notes as they arrive and old notes as they are released. This framework is designed to streamline the process of managing a list of active note objects, adding new ones and removing old ones as they come without throwing any [Concurrent Modification Exceptions.](https://docs.oracle.com/javase/7/docs/api/java/util/ConcurrentModificationException.html) The handling of this process frees up the developer to focus more on the creative aspects of the software they are writing. 8 | 9 | ## Overview 10 | 11 | The code for handling note tracking is packaged into the `NoteManager` class. The `NoteManager` assumes the existence of a `Note` class (as outlined in `Note.pde`) which has at least the following attributes and functions: 12 | ```java 13 | int lifespan; // lifespan of note, in frames 14 | boolean isReleased; // whether or not the note has been released yet 15 | void update() // updates something about the properties of the Note object 16 | void display() // generates a visual representation of the note 17 | ``` 18 | The specific implementation of this class is up to the developer, however. As soon as the `isReleased` flag becomes true, the `NoteManager` begins to decrement the `lifespan` of a note. This by default happens when a note is released, however the flag could be made true earlier if desired. 19 | 20 | The `NoteManager` itself has a few key functions which are of use to the developer: 21 | 22 | ```java 23 | void addNote(Note n) 24 | ``` 25 | This adds a new `Note` object to be tracked. Likely called every time a `noteOn` is received. 26 | 27 | ```java 28 | void releaseNote(Note n) 29 | ``` 30 | This will release the `Note` object with the same specifications as the given `Note n` from being tracked. Likely called every time a `noteOff` is received. 31 | 32 | ```java 33 | void track() 34 | ``` 35 | This function, which should be run every iteration of the `draw()` loop, tells the `NoteManager` to start tracking all the notes that were pressed since the last iteration of `draw()`, and to stop tracking any of the notes that have since been released. It also calls `update()` and then `display()` on every note currently being tracked. 36 | 37 | At any given point in the tracking, an `ArrayList` of the `Note` objects currently being tracked is stored under `NoteManager.notes`. 38 | 39 |
40 | --------------------------------------------------------------------------------