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
- Basic MIDI Synthesis: Note playing, chords, and instrument control
- Music Theory Integration: Scales, chords, and rhythm patterns
- MIDI File Generation: Creating and saving MIDI sequences
- Algorithmic Composition: Markov chains and genetic algorithms
- Real-time Performance: Interactive MIDI controllers
- Drum Programming: Rhythm patterns and drum machines
- 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.