Generate Music in your Android App
In this video it shows the steps to generate Midi music file which can be played in Android App using the media player..
I hope you like this video. For any questions, suggestions or appreciation please contact us at: https://programmerworld.co/contact/ or email at: programmerworld1990@gmail.com
Code:
MainActivity.java
package com.programmerworld.musicgenerationapp;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity {
// How to generate and play a music in your Android App?
private MediaPlayer mediaPlayer;
private SeekBar seekBar;
private TextView timeTextView;
private Handler handler = new Handler();
private Runnable updateSeekBarRunnable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
Button playMidiButton = findViewById(R.id.playMidiButton);
seekBar = findViewById(R.id.seekBar);
timeTextView = findViewById(R.id.timeTextView);
playMidiButton.setOnClickListener(v1 -> {
MidiGenerator midiGenerator = new MidiGenerator();
String midiData = null;
try {
midiData = midiGenerator.generateMidiBase64();
} catch (IOException e) {
throw new RuntimeException(e);
}
playMidi(midiData);
});
return insets;
});
}
private void playMidi(String midiData) {
try {
// Decode Base64 to binary
byte[] midiBytes = Base64.getDecoder().decode(midiData);
// Save the binary data to a .mid file
File midiFile = saveMidiToFile(midiBytes);
// Initialize MediaPlayer
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(midiFile.getAbsolutePath());
mediaPlayer.prepare(); // Prepare the media player
mediaPlayer.start(); // Start playback
// Set up SeekBar
seekBar.setMax(mediaPlayer.getDuration());
updateSeekBar();
// Update SeekBar on progress change
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
mediaPlayer.seekTo(progress);
updateTimeText();
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) { }
@Override
public void onStopTrackingTouch(SeekBar seekBar) { }
});
// Handle completion
mediaPlayer.setOnCompletionListener(mp -> {
Toast.makeText(this, "Playback completed", Toast.LENGTH_SHORT).show();
seekBar.setProgress(0);
updateTimeText();
releaseMediaPlayer();
});
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "Error: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
private File saveMidiToFile(byte[] midiBytes) throws IOException {
File midiFile = new File(getExternalFilesDir(null), "generated_music.mid");
try (FileOutputStream fos = new FileOutputStream(midiFile)) {
fos.write(midiBytes);
}
return midiFile;
}
private void updateSeekBar() {
if (mediaPlayer != null) {
seekBar.setProgress(mediaPlayer.getCurrentPosition());
updateTimeText();
// Schedule the next update
updateSeekBarRunnable = this::updateSeekBar;
handler.postDelayed(updateSeekBarRunnable, 500); // Update every 500ms
}
}
private void updateTimeText() {
if (mediaPlayer != null) {
int currentPosition = mediaPlayer.getCurrentPosition();
int totalDuration = mediaPlayer.getDuration();
String currentTime = formatTime(currentPosition);
String totalTime = formatTime(totalDuration);
timeTextView.setText(String.format("%s / %s", currentTime, totalTime));
}
}
private String formatTime(int millis) {
long minutes = TimeUnit.MILLISECONDS.toMinutes(millis);
long seconds = TimeUnit.MILLISECONDS.toSeconds(millis) % 60;
return String.format("%02d:%02d", minutes, seconds);
}
private void releaseMediaPlayer() {
if (mediaPlayer != null) {
handler.removeCallbacks(updateSeekBarRunnable);
mediaPlayer.release();
mediaPlayer = null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
releaseMediaPlayer();
}
}
MidiGenerator.java
package com.programmerworld.musicgenerationapp;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
public class MidiGenerator {
public static void main(String[] args) {
try {
String midiData = generateMidiBase64();
System.out.println("MIDI Data (Base64):");
System.out.println(midiData);
} catch (IOException e) {
e.printStackTrace();
}
}
public static String generateMidiBase64() throws IOException {
// Define MIDI constants
final int TICKS_PER_BEAT = 480;
// Initialize MIDI header
byte[] headerChunk = {
'M', 'T', 'h', 'd', // Chunk type
0, 0, 0, 6, // Header length
0, 1, // Format type
0, 1, // Number of tracks
(byte)(TICKS_PER_BEAT >> 8), (byte)(TICKS_PER_BEAT & 0xFF) // Division
};
// Track chunk start
byte[] trackChunkStart = {
'M', 'T', 'r', 'k', // Chunk type
0, 0, 0, 0 // Placeholder for track length
};
// Simple melody: (note, duration in ticks)
int[][] melody = {
{60, TICKS_PER_BEAT}, // C
{62, TICKS_PER_BEAT}, // D
{64, TICKS_PER_BEAT * 2}, // E
{65, TICKS_PER_BEAT}, // F
{64, TICKS_PER_BEAT}, // E
{62, TICKS_PER_BEAT * 2}, // D
{64, TICKS_PER_BEAT}, // E
{65, TICKS_PER_BEAT}, // F
{67, TICKS_PER_BEAT * 2}, // G
{69, TICKS_PER_BEAT}, // A
{67, TICKS_PER_BEAT}, // G
{65, TICKS_PER_BEAT * 2}, // F
{64, TICKS_PER_BEAT}, // E
{62, TICKS_PER_BEAT}, // D
{60, TICKS_PER_BEAT * 2} // C
};
// Build the track data
ByteArrayOutputStream trackData = new ByteArrayOutputStream();
trackData.write(new byte[]{0x00, (byte)0xFF, 0x51, 0x03, 0x07, (byte)0xA1, 0x20}); // Set Tempo
trackData.write(new byte[]{0x00, (byte)0xFF, 0x58, 0x04, 0x03, 0x02, 0x18, 0x08}); // Time Signature (3/4)
int currentTime = 0;
for (int[] note : melody) {
int pitch = note[0];
int duration = note[1];
// Note ON
trackData.write(encodeVariableLength(currentTime));
trackData.write(0x90); // Note ON channel 1
trackData.write(pitch);
trackData.write(64); // Velocity
// Note OFF
trackData.write(encodeVariableLength(duration));
trackData.write(0x80); // Note OFF channel 1
trackData.write(pitch);
trackData.write(64); // Velocity
currentTime = 0; // Subsequent events occur immediately
}
// End of Track
trackData.write(0x00);
trackData.write(0xFF);
trackData.write(0x2F);
trackData.write(0x00);
// Write track length
byte[] trackBytes = trackData.toByteArray();
int trackLength = trackBytes.length;
trackChunkStart[4] = (byte) ((trackLength >> 24) & 0xFF);
trackChunkStart[5] = (byte) ((trackLength >> 16) & 0xFF);
trackChunkStart[6] = (byte) ((trackLength >> 8) & 0xFF);
trackChunkStart[7] = (byte) (trackLength & 0xFF);
// Combine header and track chunks into one MIDI file
ByteArrayOutputStream midiData = new ByteArrayOutputStream();
midiData.write(headerChunk);
midiData.write(trackChunkStart);
midiData.write(trackBytes);
// Encode the binary data to Base64 and return as a string
byte[] midiBytes = midiData.toByteArray();
return Base64.getEncoder().encodeToString(midiBytes);
}
private static byte[] encodeVariableLength(int value) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int current = value & 0x7F;
while ((value >>= 7) > 0) {
buffer.write((current | 0x80));
current = value & 0x7F;
}
buffer.write(current);
return buffer.toByteArray();
}
}
activity_main.xml layout file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/timeTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00.00/00.00"
android:textSize="34sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.453" />
<Button
android:id="@+id/playMidiButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:text="Generate and Play Music"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<SeekBar
android:id="@+id/seekBar"
android:layout_width="338dp"
android:layout_height="70dp"
android:layout_marginTop="67dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/playMidiButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
Screenshots:



Music file created in the App’s data folder:
