MIDI Music Generation in Java

Introduction

MIDI (Musical Instrument Digital Interface) music generation in Java allows developers to programmatically create, manipulate, and play musical compositions. This guide covers comprehensive MIDI programming using Java's built-in MIDI API and external libraries.

Setup and Dependencies

Maven Dependencies

<dependencies>
<!-- Java Sound API (included in JDK) -->
<!-- No additional dependencies needed for basic MIDI -->
<!-- For advanced MIDI features -->
<dependency>
<groupId>com.googlecode.soundlibs</groupId>
<artifactId>jlayer</artifactId>
<version>1.0.1</version>
</dependency>
<!-- For MIDI file manipulation -->
<dependency>
<groupId>org.jfugue</groupId>
<artifactId>jfugue</artifactId>
<version>5.0.9</version>
</dependency>
</dependencies>

Basic MIDI Synthesis

Simple MIDI Player

import javax.sound.midi.*;
import java.util.ArrayList;
import java.util.List;
public class BasicMIDIPlayer {
private Synthesizer synthesizer;
private MidiChannel[] channels;
private final int DEFAULT_VELOCITY = 64;
private final int DEFAULT_CHANNEL = 0;
public BasicMIDIPlayer() throws MidiUnavailableException {
initializeSynthesizer();
}
private void initializeSynthesizer() throws MidiUnavailableException {
synthesizer = MidiSystem.getSynthesizer();
synthesizer.open();
channels = synthesizer.getChannels();
// Set default instrument (Acoustic Grand Piano)
channels[DEFAULT_CHANNEL].programChange(0);
}
public void playNote(int note, int duration) throws InterruptedException {
playNote(note, duration, DEFAULT_VELOCITY);
}
public void playNote(int note, int duration, int velocity) throws InterruptedException {
channels[DEFAULT_CHANNEL].noteOn(note, velocity);
Thread.sleep(duration);
channels[DEFAULT_CHANNEL].noteOff(note);
}
public void playChord(int[] notes, int duration) throws InterruptedException {
playChord(notes, duration, DEFAULT_VELOCITY);
}
public void playChord(int[] notes, int duration, int velocity) throws InterruptedException {
// Play all notes simultaneously
for (int note : notes) {
channels[DEFAULT_CHANNEL].noteOn(note, velocity);
}
Thread.sleep(duration);
// Turn off all notes
for (int note : notes) {
channels[DEFAULT_CHANNEL].noteOff(note);
}
}
public void changeInstrument(int program) {
channels[DEFAULT_CHANNEL].programChange(program);
}
public void setVolume(int volume) {
channels[DEFAULT_CHANNEL].controlChange(7, volume); // 7 = Volume controller
}
public void playScale(int[] scale, int noteDuration) throws InterruptedException {
for (int note : scale) {
playNote(note, noteDuration);
}
}
public void close() {
if (synthesizer != null && synthesizer.isOpen()) {
synthesizer.close();
}
}
// Demo method
public static void demo() throws Exception {
BasicMIDIPlayer player = new BasicMIDIPlayer();
// C Major scale
int[] cMajorScale = {60, 62, 64, 65, 67, 69, 71, 72}; // C4 to C5
System.out.println("Playing C Major Scale...");
player.playScale(cMajorScale, 300);
Thread.sleep(500);
// C Major chord
int[] cMajorChord = {60, 64, 67}; // C, E, G
System.out.println("Playing C Major Chord...");
player.playChord(cMajorChord, 1000);
player.close();
}
}

MIDI Note Constants and Music Theory

Music Theory Utilities

public class MusicTheory {
// MIDI note numbers for C4 scale (Middle C = 60)
public static final int C4 = 60;
public static final int CS4 = 61; // C#4/Db4
public static final int D4 = 62;
public static final int DS4 = 63; // D#4/Eb4
public static final int E4 = 64;
public static final int F4 = 65;
public static final int FS4 = 66; // F#4/Gb4
public static final int G4 = 67;
public static final int GS4 = 68; // G#4/Ab4
public static final int A4 = 69;
public static final int AS4 = 70; // A#4/Bb4
public static final int B4 = 71;
public static final int C5 = 72;
// Common scales
public static int[] getMajorScale(int root) {
// Major scale pattern: W-W-H-W-W-W-H
return new int[]{
root,
root + 2,  // Whole step
root + 4,  // Whole step
root + 5,  // Half step
root + 7,  // Whole step
root + 9,  // Whole step
root + 11, // Whole step
root + 12  // Half step (octave)
};
}
public static int[] getMinorScale(int root) {
// Natural minor scale pattern: W-H-W-W-H-W-W
return new int[]{
root,
root + 2,  // Whole step
root + 3,  // Half step
root + 5,  // Whole step
root + 7,  // Whole step
root + 8,  // Half step
root + 10, // Whole step
root + 12  // Whole step (octave)
};
}
public static int[] getPentatonicMajor(int root) {
return new int[]{
root,
root + 2,
root + 4,
root + 7,
root + 9
};
}
public static int[] getPentatonicMinor(int root) {
return new int[]{
root,
root + 3,
root + 5,
root + 7,
root + 10
};
}
// Common chords
public static int[] getMajorChord(int root) {
return new int[]{root, root + 4, root + 7};
}
public static int[] getMinorChord(int root) {
return new int[]{root, root + 3, root + 7};
}
public static int[] getDominant7th(int root) {
return new int[]{root, root + 4, root + 7, root + 10};
}
public static int[] getMajor7th(int root) {
return new int[]{root, root + 4, root + 7, root + 11};
}
// Rhythm patterns (duration in milliseconds)
public static class Rhythm {
public static final int WHOLE_NOTE = 2000;
public static final int HALF_NOTE = 1000;
public static final int QUARTER_NOTE = 500;
public static final int EIGHTH_NOTE = 250;
public static final int SIXTEENTH_NOTE = 125;
public static int[] getRockBeat() {
return new int[]{QUARTER_NOTE, QUARTER_NOTE, QUARTER_NOTE, QUARTER_NOTE};
}
public static int[] getWaltzBeat() {
return new int[]{HALF_NOTE, QUARTER_NOTE, QUARTER_NOTE};
}
}
// Instrument programs (General MIDI)
public static class Instruments {
public static final int ACOUSTIC_GRAND_PIANO = 0;
public static final int BRIGHT_ACOUSTIC_PIANO = 1;
public static final int ELECTRIC_GRAND_PIANO = 2;
public static final int HONKY_TONK_PIANO = 3;
public static final int ELECTRIC_PIANO_1 = 4;
public static final int ELECTRIC_PIANO_2 = 5;
public static final int HARPSICHORD = 6;
public static final int CLAVINET = 7;
public static final int CELESTA = 8;
public static final int GLOCKENSPIEL = 9;
public static final int MUSIC_BOX = 10;
public static final int VIBRAPHONE = 11;
public static final int MARIMBA = 12;
public static final int XYLOPHONE = 13;
public static final int TUBULAR_BELLS = 14;
public static final int DULCIMER = 15;
public static final int ORGAN = 16;
public static final int GUITAR = 24;
public static final int VIOLIN = 40;
public static final int TRUMPET = 56;
public static final int SAXOPHONE = 66;
public static final int FLUTE = 73;
// ... and many more
}
public static String getNoteName(int midiNote) {
String[] noteNames = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
int octave = (midiNote / 12) - 1;
int noteIndex = midiNote % 12;
return noteNames[noteIndex] + octave;
}
}

Advanced MIDI Sequence Generation

MIDI Sequence Composer

import javax.sound.midi.*;
import java.util.ArrayList;
import java.util.List;
public class MIDIComposer {
private Sequence sequence;
private Track track;
private int currentTick;
private final int DEFAULT_RESOLUTION = 480; // ticks per quarter note
private final int DEFAULT_TEMPO = 120; // BPM
public MIDIComposer() throws InvalidMidiDataException {
this.sequence = new Sequence(Sequence.PPQ, DEFAULT_RESOLUTION);
this.track = sequence.createTrack();
this.currentTick = 0;
setTempo(DEFAULT_TEMPO);
}
public MIDIComposer(int resolution) throws InvalidMidiDataException {
this.sequence = new Sequence(Sequence.PPQ, resolution);
this.track = sequence.createTrack();
this.currentTick = 0;
setTempo(DEFAULT_TEMPO);
}
public void setTempo(int bpm) throws InvalidMidiDataException {
int mpq = 60000000 / bpm; // microseconds per quarter note
MetaMessage tempoMessage = new MetaMessage();
byte[] data = new byte[3];
data[0] = (byte) ((mpq >> 16) & 0xFF);
data[1] = (byte) ((mpq >> 8) & 0xFF);
data[2] = (byte) (mpq & 0xFF);
tempoMessage.setMessage(0x51, data, data.length);
MidiEvent tempoEvent = new MidiEvent(tempoMessage, currentTick);
track.add(tempoEvent);
}
public void addNote(int channel, int note, int velocity, int duration, int tick) 
throws InvalidMidiDataException {
// Note On event
ShortMessage noteOn = new ShortMessage();
noteOn.setMessage(ShortMessage.NOTE_ON, channel, note, velocity);
MidiEvent onEvent = new MidiEvent(noteOn, tick);
track.add(onEvent);
// Note Off event
ShortMessage noteOff = new ShortMessage();
noteOff.setMessage(ShortMessage.NOTE_OFF, channel, note, velocity);
MidiEvent offEvent = new MidiEvent(noteOff, tick + duration);
track.add(offEvent);
}
public void addChord(int channel, int[] notes, int velocity, int duration, int tick) 
throws InvalidMidiDataException {
for (int note : notes) {
addNote(channel, note, velocity, duration, tick);
}
}
public void addProgramChange(int channel, int program) throws InvalidMidiDataException {
ShortMessage programChange = new ShortMessage();
programChange.setMessage(ShortMessage.PROGRAM_CHANGE, channel, program, 0);
MidiEvent programEvent = new MidiEvent(programChange, currentTick);
track.add(programEvent);
}
public void addControlChange(int channel, int controller, int value) 
throws InvalidMidiDataException {
ShortMessage controlChange = new ShortMessage();
controlChange.setMessage(ShortMessage.CONTROL_CHANGE, channel, controller, value);
MidiEvent controlEvent = new MidiEvent(controlChange, currentTick);
track.add(controlEvent);
}
public void addRest(int duration) {
currentTick += duration;
}
public void play() throws MidiUnavailableException, InvalidMidiDataException {
Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
sequencer.setSequence(sequence);
sequencer.start();
// Wait for playback to complete
while (sequencer.isRunning()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
sequencer.close();
}
public void saveToFile(String filename) throws java.io.IOException {
int[] supportedTypes = MidiSystem.getMidiFileTypes(sequence);
if (supportedTypes.length == 0) {
throw new java.io.IOException("No supported MIDI file types");
}
java.io.File file = new java.io.File(filename);
MidiSystem.write(sequence, supportedTypes[0], file);
System.out.println("MIDI file saved: " + filename);
}
// Composition helper methods
public void composeMelody(int channel, int[] notes, int[] durations, int velocity) 
throws InvalidMidiDataException {
for (int i = 0; i < notes.length; i++) {
addNote(channel, notes[i], velocity, durations[i], currentTick);
currentTick += durations[i];
}
}
public void composeChordProgression(int channel, int[][] chords, int duration, int velocity) 
throws InvalidMidiDataException {
for (int[] chord : chords) {
addChord(channel, chord, velocity, duration, currentTick);
currentTick += duration;
}
}
public void addArpeggio(int channel, int[] notes, int pattern, int noteDuration, int velocity) 
throws InvalidMidiDataException {
switch (pattern) {
case 1: // Up
for (int note : notes) {
addNote(channel, note, velocity, noteDuration, currentTick);
currentTick += noteDuration;
}
break;
case 2: // Down
for (int i = notes.length - 1; i >= 0; i--) {
addNote(channel, notes[i], velocity, noteDuration, currentTick);
currentTick += noteDuration;
}
break;
case 3: // Up-Down
for (int note : notes) {
addNote(channel, note, velocity, noteDuration, currentTick);
currentTick += noteDuration;
}
for (int i = notes.length - 2; i > 0; i--) {
addNote(channel, notes[i], velocity, noteDuration, currentTick);
currentTick += noteDuration;
}
break;
}
}
}

Algorithmic Music Generation

Markov Chain Music Generator

import java.util.*;
import java.util.stream.Collectors;
public class MarkovMusicGenerator {
private Map<String, List<String>> markovChain;
private Random random;
public MarkovMusicGenerator() {
this.markovChain = new HashMap<>();
this.random = new Random();
}
public void train(List<String> sequences) {
for (String sequence : sequences) {
String[] notes = sequence.split(" ");
for (int i = 0; i < notes.length - 1; i++) {
String currentNote = notes[i];
String nextNote = notes[i + 1];
markovChain.computeIfAbsent(currentNote, k -> new ArrayList<>()).add(nextNote);
}
}
}
public List<String> generateSequence(String startNote, int length) {
List<String> sequence = new ArrayList<>();
String currentNote = startNote;
sequence.add(currentNote);
for (int i = 1; i < length; i++) {
List<String> possibleNextNotes = markovChain.get(currentNote);
if (possibleNextNotes == null || possibleNextNotes.isEmpty()) {
break;
}
// Randomly select next note based on probabilities
String nextNote = possibleNextNotes.get(random.nextInt(possibleNextNotes.size()));
sequence.add(nextNote);
currentNote = nextNote;
}
return sequence;
}
public void trainFromMIDI(List<int[]> noteSequences) {
for (int[] sequence : noteSequences) {
List<String> stringSequence = Arrays.stream(sequence)
.mapToObj(MusicTheory::getNoteName)
.collect(Collectors.toList());
train(Collections.singletonList(String.join(" ", stringSequence)));
}
}
public static class GeneticMusicComposer {
private static final int POPULATION_SIZE = 50;
private static final double MUTATION_RATE = 0.1;
private static final int MAX_GENERATIONS = 1000;
public static class Melody {
private int[] notes;
private double fitness;
public Melody(int length, Random random) {
this.notes = new int[length];
for (int i = 0; i < length; i++) {
this.notes[i] = 60 + random.nextInt(13); // C4 to C5
}
}
public Melody(int[] notes) {
this.notes = notes.clone();
}
public double calculateFitness() {
// Fitness based on musical rules
double fitness = 0.0;
// Prefer melodies that stay in key
fitness += calculateKeyConsistency();
// Prefer smooth transitions
fitness += calculateSmoothness();
// Prefer interesting rhythms (variation in note lengths)
// fitness += calculateRhythmicInterest();
this.fitness = fitness;
return fitness;
}
private double calculateKeyConsistency() {
// Simple: count notes in C major scale
int[] cMajor = {0, 2, 4, 5, 7, 9, 11}; // Scale degrees
int inKeyCount = 0;
for (int note : notes) {
int noteInOctave = note % 12;
if (Arrays.stream(cMajor).anyMatch(x -> x == noteInOctave)) {
inKeyCount++;
}
}
return (double) inKeyCount / notes.length;
}
private double calculateSmoothness() {
double smoothness = 0.0;
for (int i = 1; i < notes.length; i++) {
int interval = Math.abs(notes[i] - notes[i - 1]);
if (interval <= 2) { // Stepwise motion
smoothness += 1.0;
} else if (interval <= 5) { // Small leaps
smoothness += 0.5;
}
// Large leaps reduce smoothness
}
return smoothness / (notes.length - 1);
}
public Melody crossover(Melody other, Random random) {
int crossoverPoint = random.nextInt(notes.length);
int[] childNotes = new int[notes.length];
System.arraycopy(notes, 0, childNotes, 0, crossoverPoint);
System.arraycopy(other.notes, crossoverPoint, childNotes, crossoverPoint, 
notes.length - crossoverPoint);
return new Melody(childNotes);
}
public void mutate(Random random) {
for (int i = 0; i < notes.length; i++) {
if (random.nextDouble() < MUTATION_RATE) {
// Mutate this note
int mutation = random.nextInt(3) - 1; // -1, 0, or 1
notes[i] = Math.max(0, Math.min(127, notes[i] + mutation));
}
}
}
public int[] getNotes() { return notes.clone(); }
public double getFitness() { return fitness; }
}
public static Melody evolveMelody(int length, Random random) {
List<Melody> population = new ArrayList<>();
// Initialize population
for (int i = 0; i < POPULATION_SIZE; i++) {
population.add(new Melody(length, random));
}
// Evolution loop
for (int generation = 0; generation < MAX_GENERATIONS; generation++) {
// Evaluate fitness
population.forEach(Melody::calculateFitness);
// Sort by fitness
population.sort((m1, m2) -> Double.compare(m2.getFitness(), m1.getFitness()));
// Check for convergence
if (population.get(0).getFitness() > 0.9) {
System.out.println("Converged at generation " + generation);
break;
}
// Create new generation
List<Melody> newPopulation = new ArrayList<>();
// Elitism: keep best individuals
int eliteCount = POPULATION_SIZE / 10;
for (int i = 0; i < eliteCount; i++) {
newPopulation.add(population.get(i));
}
// Crossover and mutation
while (newPopulation.size() < POPULATION_SIZE) {
Melody parent1 = selectParent(population, random);
Melody parent2 = selectParent(population, random);
Melody child = parent1.crossover(parent2, random);
child.mutate(random);
newPopulation.add(child);
}
population = newPopulation;
if (generation % 100 == 0) {
System.out.printf("Generation %d: Best fitness = %.3f%n", 
generation, population.get(0).getFitness());
}
}
return population.get(0);
}
private static Melody selectParent(List<Melody> population, Random random) {
// Tournament selection
int tournamentSize = 5;
Melody best = null;
for (int i = 0; i < tournamentSize; i++) {
Melody candidate = population.get(random.nextInt(population.size()));
if (best == null || candidate.getFitness() > best.getFitness()) {
best = candidate;
}
}
return best;
}
}
}

Real-Time MIDI Performance

Interactive MIDI Controller

import javax.sound.midi.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.HashMap;
import java.util.Map;
public class InteractiveMIDIController extends JFrame {
private Synthesizer synthesizer;
private MidiChannel[] channels;
private Map<Integer, Long> activeNotes;
private int currentInstrument;
private int currentOctave;
public InteractiveMIDIController() throws MidiUnavailableException {
initializeMIDI();
initializeUI();
this.activeNotes = new HashMap<>();
this.currentInstrument = 0;
this.currentOctave = 4;
}
private void initializeMIDI() throws MidiUnavailableException {
synthesizer = MidiSystem.getSynthesizer();
synthesizer.open();
channels = synthesizer.getChannels();
channels[0].programChange(currentInstrument);
}
private void initializeUI() {
setTitle("Interactive MIDI Controller");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
// Piano keyboard
JPanel pianoPanel = createPianoKeyboard();
add(pianoPanel, BorderLayout.CENTER);
// Control panel
JPanel controlPanel = createControlPanel();
add(controlPanel, BorderLayout.NORTH);
pack();
setLocationRelativeTo(null);
}
private JPanel createPianoKeyboard() {
JPanel pianoPanel = new JPanel();
pianoPanel.setLayout(new GridLayout(1, 12));
String[] whiteKeys = {"C", "D", "E", "F", "G", "A", "B"};
String[] blackKeys = {"C#", "D#", "", "F#", "G#", "A#", ""};
for (int i = 0; i < 7; i++) {
// White key
JButton whiteKey = new JButton(whiteKeys[i]);
whiteKey.setBackground(Color.WHITE);
whiteKey.setPreferredSize(new Dimension(60, 200));
whiteKey.addMouseListener(new PianoKeyListener(
MusicTheory.C4 + i * 2 - (i >= 3 ? 1 : 0) // Adjust for E-F and B-C
));
pianoPanel.add(whiteKey);
// Black key (if exists)
if (!blackKeys[i].isEmpty()) {
JButton blackKey = new JButton(blackKeys[i]);
blackKey.setBackground(Color.BLACK);
blackKey.setForeground(Color.WHITE);
blackKey.setPreferredSize(new Dimension(40, 120));
blackKey.addMouseListener(new PianoKeyListener(
MusicTheory.C4 + i * 2 - (i >= 3 ? 1 : 0) + 1
));
// Position black key above white key
blackKey.setBounds(0, 0, 40, 120);
pianoPanel.add(blackKey);
}
}
// Add high C
JButton highC = new JButton("C");
highC.setBackground(Color.WHITE);
highC.setPreferredSize(new Dimension(60, 200));
highC.addMouseListener(new PianoKeyListener(MusicTheory.C5));
pianoPanel.add(highC);
return pianoPanel;
}
private JPanel createControlPanel() {
JPanel controlPanel = new JPanel(new FlowLayout());
// Instrument selector
JComboBox<String> instrumentCombo = new JComboBox<>(new String[]{
"Piano", "Guitar", "Violin", "Trumpet", "Flute", "Drums"
});
instrumentCombo.addActionListener(e -> {
int[] instrumentPrograms = {0, 24, 40, 56, 73, 0};
currentInstrument = instrumentPrograms[instrumentCombo.getSelectedIndex()];
channels[0].programChange(currentInstrument);
});
// Octave selector
JSpinner octaveSpinner = new JSpinner(new SpinnerNumberModel(4, 1, 7, 1));
octaveSpinner.addChangeListener(e -> {
currentOctave = (Integer) octaveSpinner.getValue();
});
// Volume slider
JSlider volumeSlider = new JSlider(0, 127, 64);
volumeSlider.addChangeListener(e -> {
channels[0].controlChange(7, volumeSlider.getValue());
});
controlPanel.add(new JLabel("Instrument:"));
controlPanel.add(instrumentCombo);
controlPanel.add(new JLabel("Octave:"));
controlPanel.add(octaveSpinner);
controlPanel.add(new JLabel("Volume:"));
controlPanel.add(volumeSlider);
return controlPanel;
}
private class PianoKeyListener extends MouseAdapter {
private final int baseNote;
public PianoKeyListener(int baseNote) {
this.baseNote = baseNote;
}
@Override
public void mousePressed(MouseEvent e) {
int note = baseNote + (currentOctave - 4) * 12;
channels[0].noteOn(note, 64);
activeNotes.put(note, System.currentTimeMillis());
}
@Override
public void mouseReleased(MouseEvent e) {
int note = baseNote + (currentOctave - 4) * 12;
channels[0].noteOff(note);
activeNotes.remove(note);
}
}
public void playArpeggio(int[] notes, int pattern, int tempo) {
new Thread(() -> {
try {
switch (pattern) {
case 1: // Up
for (int note : notes) {
channels[0].noteOn(note + currentOctave * 12, 64);
Thread.sleep(tempo);
channels[0].noteOff(note + currentOctave * 12);
}
break;
case 2: // Down
for (int i = notes.length - 1; i >= 0; i--) {
channels[0].noteOn(notes[i] + currentOctave * 12, 64);
Thread.sleep(tempo);
channels[0].noteOff(notes[i] + currentOctave * 12);
}
break;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
public static void main(String[] args) {
try {
SwingUtilities.invokeLater(() -> {
try {
new InteractiveMIDIController().setVisible(true);
} catch (MidiUnavailableException e) {
e.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}

Drum Machine and Rhythm Generation

Drum Pattern Sequencer

public class DrumMachine {
private static final int DRUM_CHANNEL = 9; // Channel 10 in MIDI (0-based)
// General MIDI drum notes
public static class DrumNotes {
public static final int BASS_DRUM_2 = 35;
public static final int BASS_DRUM_1 = 36;
public static final int SIDE_STICK = 37;
public static final int SNARE_DRUM_1 = 38;
public static final int HAND_CLAP = 39;
public static final int SNARE_DRUM_2 = 40;
public static final int LOW_TOM_2 = 41;
public static final int CLOSED_HI_HAT = 42;
public static final int LOW_TOM_1 = 43;
public static final int PEDAL_HI_HAT = 44;
public static final int MID_TOM_2 = 45;
public static final int OPEN_HI_HAT = 46;
public static final int MID_TOM_1 = 47;
public static final int HIGH_TOM_2 = 48;
public static final int CRASH_CYMBAL_1 = 49;
public static final int HIGH_TOM_1 = 50;
public static final int RIDE_CYMBAL_1 = 51;
public static final int CHINESE_CYMBAL = 52;
public static final int RIDE_BELL = 53;
public static final int TAMBOURINE = 54;
public static final int SPLASH_CYMBAL = 55;
public static final int COWBELL = 56;
public static final int CRASH_CYMBAL_2 = 57;
public static final int VIBRASLAP = 58;
public static final int RIDE_CYMBAL_2 = 59;
public static final int HIGH_BONGO = 60;
public static final int LOW_BONGO = 61;
public static final int MUTE_HIGH_CONGA = 62;
public static final int OPEN_HIGH_CONGA = 63;
public static final int LOW_CONGA = 64;
public static final int HIGH_TIMBALE = 65;
public static final int LOW_TIMBALE = 66;
public static final int HIGH_AGOGO = 67;
public static final int LOW_AGOGO = 68;
public static final int CABASA = 69;
public static final int MARACAS = 70;
public static final int SHORT_WHISTLE = 71;
public static final int LONG_WHISTLE = 72;
public static final int SHORT_GUIRO = 73;
public static final int LONG_GUIRO = 74;
public static final int CLAVES = 75;
public static final int HIGH_WOOD_BLOCK = 76;
public static final int LOW_WOOD_BLOCK = 77;
public static final int MUTE_CUICA = 78;
public static final int OPEN_CUICA = 79;
public static final int MUTE_TRIANGLE = 80;
public static final int OPEN_TRIANGLE = 81;
}
public static class DrumPattern {
private boolean[][] pattern;
private int steps;
private int instruments;
public DrumPattern(int steps, int instruments) {
this.steps = steps;
this.instruments = instruments;
this.pattern = new boolean[instruments][steps];
}
public void setHit(int instrument, int step) {
if (instrument >= 0 && instrument < instruments && step >= 0 && step < steps) {
pattern[instrument][step] = true;
}
}
public void clearHit(int instrument, int step) {
if (instrument >= 0 && instrument < instruments && step >= 0 && step < steps) {
pattern[instrument][step] = false;
}
}
public boolean isHit(int instrument, int step) {
return pattern[instrument][step];
}
public int getSteps() { return steps; }
public int getInstruments() { return instruments; }
}
public static void playDrumPattern(MIDIComposer composer, DrumPattern pattern, 
int[] drumNotes, int stepDuration) throws InvalidMidiDataException {
for (int step = 0; step < pattern.getSteps(); step++) {
for (int instrument = 0; instrument < pattern.getInstruments(); instrument++) {
if (pattern.isHit(instrument, step)) {
composer.addNote(DRUM_CHANNEL, drumNotes[instrument], 64, 
stepDuration / 2, step * stepDuration);
}
}
}
}
// Common drum patterns
public static DrumPattern createRockBeat(int steps) {
DrumPattern pattern = new DrumPattern(steps, 3);
int[] bassDrum = {0, 4, 8, 12};
int[] snareDrum = {4, 12};
int[] hiHat = {0, 2, 4, 6, 8, 10, 12, 14};
for (int step : bassDrum) pattern.setHit(0, step);
for (int step : snareDrum) pattern.setHit(1, step);
for (int step : hiHat) pattern.setHit(2, step);
return pattern;
}
public static DrumPattern createJazzBeat(int steps) {
DrumPattern pattern = new DrumPattern(steps, 4);
// Ride cymbal pattern
for (int i = 0; i < steps; i += 2) {
pattern.setHit(0, i);
}
// Comping pattern
pattern.setHit(1, 4);
pattern.setHit(1, 12);
pattern.setHit(2, 8);
pattern.setHit(3, 0);
return pattern;
}
}

Complete Music Generation Example

Song Composer Demo

public class SongComposerDemo {
public static void main(String[] args) {
try {
// Demo 1: Simple melody
demoSimpleMelody();
// Demo 2: Chord progression
demoChordProgression();
// Demo 3: Algorithmic composition
demoAlgorithmicComposition();
// Demo 4: Full song with drums
demoFullSong();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void demoSimpleMelody() throws Exception {
System.out.println("=== Simple Melody Demo ===");
MIDIComposer composer = new MIDIComposer();
// C Major scale melody
int[] melodyNotes = {
MusicTheory.C4, MusicTheory.E4, MusicTheory.G4, MusicTheory.C5,
MusicTheory.G4, MusicTheory.E4, MusicTheory.C4
};
int[] durations = {
composer.getResolution(), composer.getResolution(), composer.getResolution(), 
composer.getResolution() * 2, composer.getResolution(), composer.getResolution(), 
composer.getResolution() * 2
};
composer.composeMelody(0, melodyNotes, durations, 80);
composer.saveToFile("simple_melody.mid");
composer.play();
}
private static void demoChordProgression() throws Exception {
System.out.println("=== Chord Progression Demo ===");
MIDIComposer composer = new MIDIComposer();
composer.setTempo(100);
// Common chord progression: I - V - vi - IV
int[][] chords = {
MusicTheory.getMajorChord(MusicTheory.C4),  // C Major
MusicTheory.getMajorChord(MusicTheory.G4),  // G Major
MusicTheory.getMinorChord(MusicTheory.A4),  // A Minor
MusicTheory.getMajorChord(MusicTheory.F4)   // F Major
};
composer.composeChordProgression(0, chords, composer.getResolution() * 4, 80);
composer.saveToFile("chord_progression.mid");
composer.play();
}
private static void demoAlgorithmicComposition() throws Exception {
System.out.println("=== Algorithmic Composition Demo ===");
// Generate melody using genetic algorithm
MarkovMusicGenerator.GeneticMusicComposer.Melody evolvedMelody = 
MarkovMusicGenerator.GeneticMusicComposer.evolveMelody(16, new Random());
MIDIComposer composer = new MIDIComposer();
int[] melody = evolvedMelody.getNotes();
int[] durations = new int[melody.length];
Arrays.fill(durations, composer.getResolution());
composer.composeMelody(0, melody, durations, 80);
composer.saveToFile("algorithmic_melody.mid");
composer.play();
}
private static void demoFullSong() throws Exception {
System.out.println("=== Full Song Demo ===");
MIDIComposer composer = new MIDIComposer();
composer.setTempo(120);
// Set instruments
composer.addProgramChange(0, MusicTheory.Instruments.ACOUSTIC_GRAND_PIANO); // Melody
composer.addProgramChange(1, MusicTheory.Instruments.ELECTRIC_BASS_FINGER); // Bass
composer.addProgramChange(2, MusicTheory.Instruments.ORCHESTRAL_STRINGS);   // Strings
// Chord progression
int[][] chords = {
MusicTheory.getMajorChord(MusicTheory.C4),
MusicTheory.getMajorChord(MusicTheory.G4),
MusicTheory.getMinorChord(MusicTheory.A4),
MusicTheory.getMajorChord(MusicTheory.F4)
};
// Melody based on chords
int[][] melodyNotes = {
{MusicTheory.C5, MusicTheory.E5, MusicTheory.G5},
{MusicTheory.B4, MusicTheory.D5, MusicTheory.G5},
{MusicTheory.C5, MusicTheory.E5, MusicTheory.A5},
{MusicTheory.A4, MusicTheory.C5, MusicTheory.F5}
};
// Bass line (root notes)
int[] bassNotes = {
MusicTheory.C3, MusicTheory.G3, MusicTheory.A3, MusicTheory.F3
};
// Compose 4 bars
for (int bar = 0; bar < 4; bar++) {
int tick = bar * composer.getResolution() * 4;
// Chords (piano)
composer.addChord(0, chords[bar % chords.length], 70, 
composer.getResolution() * 4, tick);
// Melody (strings)
for (int i = 0; i < 4; i++) {
int melodyTick = tick + i * composer.getResolution();
int note = melodyNotes[bar % melodyNotes.length]
[i % melodyNotes[bar % melodyNotes.length].length];
composer.addNote(2, note, 80, composer.getResolution(), melodyTick);
}
// Bass
composer.addNote(1, bassNotes[bar % bassNotes.length], 90,
composer.getResolution() * 4, tick);
}
// Add drums
DrumMachine.DrumPattern drumPattern = DrumMachine.createRockBeat(16);
int[] drumInstruments = {
DrumMachine.DrumNotes.BASS_DRUM_1,
DrumMachine.DrumNotes.SNARE_DRUM_1,
DrumMachine.DrumNotes.CLOSED_HI_HAT
};
DrumMachine.playDrumPattern(composer, drumPattern, drumInstruments, 
composer.getResolution());
composer.saveToFile("full_song.mid");
composer.play();
}
}

Key Features Covered

  1. Basic MIDI Synthesis: Note playing, chords, and instrument control
  2. Music Theory Integration: Scales, chords, and rhythm patterns
  3. MIDI File Generation: Creating and saving MIDI sequences
  4. Algorithmic Composition: Markov chains and genetic algorithms
  5. Real-time Performance: Interactive MIDI controllers
  6. Drum Programming: Rhythm patterns and drum machines
  7. Multi-track Composition: Layering melodies, chords, and rhythms

This comprehensive MIDI music generation framework provides everything needed to create algorithmic music, interactive performances, and professional-quality MIDI compositions in Java.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper