MIDI Interface for Processing Project

Image courtesy of Saranont Limpananont

The Limitations of Physical User Interfaces for Digital Art Expression:

I mainly use a regular computer keyboard as an interface to send input for making visual art in programs. However, as programs become more complex, I need to assign more keys as inputs until I eventually run out of keys for working on my projects.

Creating an onscreen slider interface would be a better approach - that should be prioritized instead of continuously assigning additional keyboard keys. The next challenge is that the onscreen sliders require a mouse to control, but I only have one cursor. So I have to click and drag each individual slider. While this interface provides a solution, it also introduces a new limitation. It counters intuition as a tool for making art.

Ironically, I've had a Roland SE-02 analog synthesizer sit right on my desk for years, originally intended for a sound project. Moreover, I've already connected and set up this device on my computer. It could potentially serve as an interface alternative to a standard keyboard or mouse. The tricky aspect for a relative beginner like myself was the MIDI mapping process which knobs or buttons corresponded to which MIDI numbers since the SE-02 is not a typical MIDI keyboard controller.

Using the Roland SE-02 as a hardware interface for sending data to control the visual elements of the work.


MIDI Mapping Template for Processing:

The simple Processing code below, powered by the MidiBus library, displays the MIDI input number and value from any connected device. While this might not be the most comprehensive Processing tutorial, it should be helpful for anyone looking to connect a MIDI device with Processing, especially if they can't locate the user manual or are uncertain about the specific knob mappings.

This is the sample code. Feel free to use and modify:

/**
 * MIDI Mapping Template for Processing
 *
 * Description:
 * This program demonstrates interaction with MIDI devices using the Processing environment and the MidiBus library.
 * It allows users to select a MIDI device, receive various types of MIDI messages, 
 * and map these messages to specific actions.
 *
 * Usage:
 * 1. List available MIDI devices using the console output.
 * 2. Select your MIDI device by entering its name in the setup function.
 * 3. Customize the mappings in the respective MIDI message functions.
 * 4. Run the sketch and interact with your MIDI device to see messages and actions in the console and on the screen.
 *
 * Date: 2024-06-15
 * 
 * Note:
 * - Ensure the MidiBus library is installed and imported correctly.
 * - Replace the placeholder device name with the actual MIDI device name.
 */

import themidibus.*; // Import the MidiBus library

MidiBus myBus; // Declare a MidiBus object

// Variables to store the last MIDI message data
int lastPitch = -1; 
int lastVelocity = -1;
int lastControllerNumber = -1;
int lastControllerValue = -1;

void setup() {
  size(400, 400); // Set the size of the window
  MidiBus.list(); // List all available MIDI devices to the console
  
  // Replace "YOUR_MIDI_DEVICE_NAME" with the name of your MIDI device
  myBus = new MidiBus(this, "YOUR_MIDI_DEVICE_NAME", "YOUR_MIDI_DEVICE_NAME"); 
}

void draw() {
  background(0); // Set the background color to black
  
  // Display last received MIDI message
  fill(255); // Set the text color to white
  textSize(14); // Set a consistent text size
  text("MIDI MAPPING TEMPLATE", 20, 50);
  text("Last Note Played - Pitch: " + lastPitch + " Velocity: " + lastVelocity, 20, 80);
  text("Last Control Change - Number: " + lastControllerNumber + " Value: " + lastControllerValue, 20, 110);
}

/*
 * This function is called when a Note On message is received.
 * Parameters:
 * - channel: The MIDI channel (1-16)
 * - pitch: The note pitch (0-127)
 * - velocity: The note velocity (0-127)
 */
void noteOn(int channel, int pitch, int velocity) {
  println("Note On received:");
  println("Channel: " + channel);
  println("Pitch: " + pitch);
  println("Velocity: " + velocity);
  
  // Store the pitch and velocity to use in the draw function
  lastPitch = pitch;
  lastVelocity = velocity;
  
  // Example action: change background color based on pitch
  background(map(pitch, 0, 127, 0, 255), 100, 150);
}

/*
 * This function is called when a Note Off message is received.
 * Parameters:
 * - channel: The MIDI channel (1-16)
 * - pitch: The note pitch (0-127)
 * - velocity: The note velocity (0-127)
 */
void noteOff(int channel, int pitch, int velocity) {
  println("Note Off received:");
  println("Channel: " + channel);
  println("Pitch: " + pitch);
  println("Velocity: " + velocity);
  
  // Reset the pitch and velocity
  lastPitch = -1;
  lastVelocity = -1;
}

/*
 * This function is called when a Control Change message is received.
 * Parameters:
 * - channel: The MIDI channel (1-16)
 * - number: The controller number (0-127)
 * - value: The controller value (0-127)
 */
void controllerChange(int channel, int number, int value) {
  println("Control Change received:");
  println("Channel: " + channel);
  println("Number: " + number);
  println("Value: " + value);
  
  // Store the controller number and value to use in the draw function
  lastControllerNumber = number;
  lastControllerValue = value;
  
  // Example action: you can add other actions here based on the controller value
}

/*
 * This function is called when a Program Change message is received.
 * Parameters:
 * - channel: The MIDI channel (1-16)
 * - number: The program number (0-127)
 */
void programChange(int channel, int number) {
  println("Program Change received:");
  println("Channel: " + channel);
  println("Program Number: " + number);
  
  // Example action: print program change to console
}

/*
 * This function is called when a Pitch Bend message is received.
 * Parameters:
 * - channel: The MIDI channel (1-16)
 * - value: The pitch bend value (0-16383)
 */
void pitchBend(int channel, int value) {
  println("Pitch Bend received:");
  println("Channel: " + channel);
  println("Value: " + value);
  
  // Example action: change background color intensity based on pitch bend value
  background(map(value, 0, 16383, 0, 255)); 
}

/*
 * This function is called when a Polyphonic Key Pressure (Aftertouch) message is received.
 * Parameters:
 * - channel: The MIDI channel (1-16)
 * - pitch: The note pitch (0-127)
 * - pressure: The pressure value (0-127)
 */
void polyPressure(int channel, int pitch, int pressure) {
  println("Poly Pressure received:");
  println("Channel: " + channel);
  println("Pitch: " + pitch);
  println("Pressure: " + pressure);
}

/*
 * This function is called when a Channel Pressure (Aftertouch) message is received.
 * Parameters:
 * - channel: The MIDI channel (1-16)
 * - pressure: The pressure value (0-127)
 */
void channelPressure(int channel, int pressure) {
  println("Channel Pressure received:");
  println("Channel: " + channel);
  println("Pressure: " + pressure);
}
test

The data from a MIDI device will be displayed on the program screen in relation to the interaction with that MIDI device.

The idea is that interacting with the device will show the values within Processing (both the console and screen). So you can visually identify the MIDI numbers and values of knobs before assigning and mapping them in your project.


 

I keep the sliding bars associated with the project, as they are very helpful both as a user interface and as a visual indicator.

MIDI Interface for Processing Projects | A Composition of Broken Pixels

ความสนุกอย่างหนึ่งในกระบวนการทำงานใดใด คือการได้ลองพัฒนาเครื่องมือเครื่องใช้ที่เหมาะสมกับความต้องการขึ้นมาเพื่อใช้เอง (บางอย่างเขาก็มีขายสำเร็จรูป แต่ถ้าเราทำเองได้ก็สนุกดีครับ) 

ที่ผ่านมาในงาน generative drawing นั้น ด้วยความประหยัด ผมมักจะใช้วิธีเขียนคำสั่งให้สามารถใช้แป้นพิมพ์ส่งข้อมูลไปที่ตัวโปรแกรมได้แบบเรียลไทม์ การปฏิสัมพันธ์ระหว่างนิ้วมือกับเครื่องมือจึงเป็นเหมือนการทำงานร่วมกันที่นำไปสู่ผลลัพธ์ของงานที่เปลี่ยนไปตามตัวแปรที่เปลี่ยนแปลงจากการกดแป้นพิมพ์ตำแหน่งต่าง ๆ (คล้าย ๆ การเล่นเกมด้วยแป้นพิมพ์)  

แต่พอโปรแกรมที่เขียนเริ่มมีความสลับซับซ้อนขึ้น จำนวนปุ่มบนแป้นพิมพ์เริ่มไม่เพียงพอต่อความต้องการ ผมเลยใช้วิธีการเขียน slider bars เพิ่มเติม เพื่อควบคุมผ่านการลากเมาส์บนจอภาพ ซึ่งวิธีการนี้ช่วยแก้ปัญหาเรื่องการแบ่งเบาจำนวนแป้นที่ต้องกดเพื่อส่งคำสั่งไปได้ แต่การใช้เมาส์นั้นก็มีข้อจำกัดเฉพาะของตัวเอง คือการปฏิสัมพันธ์ของเมาส์นั้นจะทำได้เพียง 1 parameter ต่อครั้ง ผ่านการใช้ข้อมือลากเมาส์ไปกดแล้วลากซ้ายขวาทีละช่อง กระบวนการทำงานด้วยเมาส์ก็เลยฝืนสัญชาตญาณ เนื่องจากการปฏิสัมพันธ์กับคอมพิวเตอร์ในการทำงานลักษณะนี้นั้นความคิดกับความรู้สึกควรจะพัฒนาไปพร้อมกันได้ ถ้าเป็นงานออกแบบที่ใช้ความคิดไตร่ตรอง การใช้เมาส์ค่อย ๆ ลากก็อาจจะพอได้ แต่พอเป็นการทำงานศิลปะที่เกี่ยวกับความรู้สึกชั่วขณะนั้น ผมคิดว่าอินเตอร์เฟซแบบกายภาพที่ทำงานได้หลายปุ่มพร้อมกันน่าจะเหมาะกว่า

เนื่องจากผมมีเครื่องซินธิไซเซอร์ SE-02 ที่ปรกติใช้สำหรับทำงานสังเคราะห์เสียงตัวนี้วางอยู่บนโต๊ะทำงานมานานจนฝุ่นเกาะแล้ว มองไปก็เห็นว่ามันมีปุ่มเยอะดี คิดว่าถ้าเช่นนั้นก็น่าจะใช้เครื่องนี้มาทดลองเป็นตัวส่งข้อมูลแทนแป้นพิมพ์กับเมาส์ก่อน ถึงเขาจะไม่ได้ออกแบบมาสำหรับใช้งานเป็น MIDI Controller แต่มันก็มี MIDI I/O ก็น่าจะใช้งานได้ หลังจากที่ได้นำอุปกรณ์ชิ้นนี้มาปรับต่อเข้ากับ Processing ก็พบว่าใช้งานได้ดี ตรงกับการใช้งานกว่าการใช้ทั้งแป้นพิมพ์หรือเมาส์, ไม่ฝืนสัญชาติญาณ, ทำงานได้สองมือ, มีช่องส่งข้อมูลมากมายทั้งปุ่มแบบหมุน/กด ฯลฯ สามารถใช้เป็นอุปกรณ์ทำงานด้านการสังเคราะห์ภาพได้ไม่ต่างจากที่เขาออกแบบมาไว้ใช้ในการสังเคราะห์เสียง โดยรวมคือเป็นที่น่าพอใจมากครับ

ผมแชร์ code สำหรับการหาค่า/หมายเลขของ MIDI จากฮาร์ดแวร์ไว้ข้างบนนะครับ เป็น code พื้นฐานสำหรับ Processing ที่แสดงค่า Pitch, Velocity, Number, และ Value ที่รับเข้ามาจากเครื่องส่ง MIDI เพื่อให้เราสามารถตรวจสอบและนำข้อมูลตัวเลขของแต่ละปุ่มไปใช้เชื่อมโยง (map) กับการออกแบบและเขียนคำสั่งในโปรแกรมอื่น ๆ ได้ครับ สามารถนำไปลองปรับใช้ได้ โดยเปลี่ยนชื่อ "YOUR_MIDI_DEVICE_NAME" เป็นชื่อตรงกับอุปกรณ์ MIDI ที่จะนำมาใช้ก่อนครับ

myBus = new MidiBus(this, "YOUR_MIDI_DEVICE_NAME" , "YOUR_MIDI_DEVICE_NAME" );

โดยส่วนตัวเวลาทำงานลักษณะนี้ผมจะรู้สึกถึงความ nostalgia พอสมควร เหมือนได้กลับไปใช้ความรู้และเทคโนโลยีตั้งแต่เมื่อ 20 ปีก่อนให้มีประโยชน์ หวังว่าจะมีประโยชน์กับท่านอื่นด้วยครับ

ขอบคุณครับ

Saranont Limpananont

2024.06.15

*These sketches were made using Processing, visual art, and interactive media programming language.