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.