本人借鉴了两篇文章:
https://www.midifan.com/modulearticle-detailview-901.htm 理论篇
https://www.midifan.com/modulearticle-detailview-902.htm 时间篇
实现了MidiRecordTool Midi录制类,
使用的流程是:
1、构造MidiRecordTool
2、start()开始的时候调用一次
3、writeEvent(x,x,x,x)开始写midi事件—周而复始的调用
4、finish()结束的时候调用一次
5、saveMidiToFile()将录制的midi文件保存,或调用getMidiBuffer获取内存中的midi数据
代码如下:
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
/**
* Created by mwt on 2017/8/29.
* Single track
*/
public class MidiRecordTool {
//private final String TAG = "MidiRecordSingleTrack";
private MidiHead mMidiHead;
private TrackHead mTrackHead;
private EventStorageSpace mEventStorageSpace;
private byte[] mBuffer;
private long mSystemTime;
private int mOneTickTime;//一个Tick的微妙数
public final static byte[] mFinishBytes = {0x00, (byte) 0x91, 0x3C, 0x00, 0x00, (byte) 0xFF, 0x2F, 0x00};
// name:曲谱名称 division:一个四分音符的tick数 speed:1分钟有多少个四分音符 numerator:分子 denominator:分母
public MidiRecordTool(String name, int division, int speed, int numerator, int denominator) throws Exception {
if (name == null)
throw new Exception("String track name equle NULL");
byte[] trackName = name.getBytes("UTF-8");
mMidiHead = new MidiHead(division);
mTrackHead = new TrackHead(trackName, speed, numerator, denominator);
mOneTickTime = (int) ((1000000.0 * 60 / speed) / division);
int headLen = mMidiHead.getMidiHeadLength() + mTrackHead.getTrackHeadLength();
mEventStorageSpace = new EventStorageSpace(headLen);
//Log.i(TAG, " mEventPosion:" + mEventPosion);
mSystemTime = System.currentTimeMillis();
}
//开始录音调用一次
public void start() {
mSystemTime = System.currentTimeMillis();
}
//channel:通道 code:事件码 press:是否按下 speed:力度
public synchronized void writeEvent(int channel, int code, boolean press, int speed) {
long tmpTime = System.currentTimeMillis();
long waste = tmpTime - mSystemTime;
mSystemTime = tmpTime;
long ticks = (waste * 1000 / mOneTickTime);
byte[] time = trackTimeToBytes(ticks);
int len = time.length + 3;//
byte[] buffer = new byte[len];
System.arraycopy(time, 0, buffer, 0, time.length);
if (press)
buffer[time.length] = (byte) (0x90 | (0xF & channel));
else
buffer[time.length] = (byte) (0x80 | (0xF & channel));
buffer[time.length + 1] = (byte) (0xFF & code);
buffer[time.length + 2] = (byte) (0xFF & speed);
//mEventStorageSpace.writeEvent(buffer, len);
writeEvent(buffer, len);
}
private void writeEvent(byte[] buffer, int len) {
mEventStorageSpace.writeEvent(buffer, len);
}
//录音结束调用一次
public void finish() {
if (mBuffer == null) {
writeEvent(mFinishBytes, mFinishBytes.length);
mBuffer = mEventStorageSpace.getTotalBuffer();
byte[] midiHead = mMidiHead.getMidiHeadBytes();
byte[] trackHead = mTrackHead.getTrackHeadBytes(mEventStorageSpace.getEventLength());
System.arraycopy(midiHead, 0, mBuffer, 0, midiHead.length);
System.arraycopy(trackHead, 0, mBuffer, midiHead.length, trackHead.length);
}
}
//保存录音曲谱
public void saveMidiToFile(String fileName) throws IOException {
//File file = new File(path);
FileOutputStream fout = new FileOutputStream(fileName);
if (mBuffer == null) {
//结束
mEventStorageSpace.writeEvent(mFinishBytes, mFinishBytes.length);
mBuffer = mEventStorageSpace.getTotalBuffer();
byte[] midiHead = mMidiHead.getMidiHeadBytes();
byte[] trackHead = mTrackHead.getTrackHeadBytes(mEventStorageSpace.getEventLength());
System.arraycopy(midiHead, 0, mBuffer, 0, midiHead.length);
System.arraycopy(trackHead, 0, mBuffer, midiHead.length, trackHead.length);
}
fout.write(mBuffer);
fout.close();
}
//获取录音内容
public byte[] getMidiBuffer() {
if (mBuffer == null) {
//结束
mEventStorageSpace.writeEvent(mFinishBytes, mFinishBytes.length);
mBuffer = mEventStorageSpace.getTotalBuffer();
byte[] midiHead = mMidiHead.getMidiHeadBytes();
byte[] trackHead = mTrackHead.getTrackHeadBytes(mEventStorageSpace.getEventLength());
System.arraycopy(midiHead, 0, mBuffer, 0, midiHead.length);
System.arraycopy(trackHead, 0, mBuffer, midiHead.length, trackHead.length);
}
return mBuffer;
}
private byte[] trackTimeToBytes(long time) {
byte[] tmp = new byte[8];
int n = 0;
if (time <= 0) {
byte[] ret = {0x00};
return ret;
}
while (time > 127) {
tmp[n] = (byte) (time % 128);
time = time / 128;
n++;
}
if (time > 0) {
tmp[n] = (byte) time;
n++;
}
//除第一个字节,其它自己都加上0x80
for (int i = 1; i < n; i++) {
tmp[i] |= 0x80;
}
//高低位翻转
byte[] result = new byte[n];
for (int i = 0; i < n; i++) {
result[i] = tmp[n - i - 1];
}
return result;
}
private byte[] swap2byte(int value) {
byte[] result = new byte[2];
result[0] = (byte) (value >> 8 & 0xFF);
result[1] = (byte) (value & 0xFF);
return result;
}
private byte[] swap3byte(int value) {
byte[] result = new byte[3];
result[0] = (byte) (value >> 16 & 0xFF);
result[1] = (byte) (value >> 8 & 0xFF);
result[2] = (byte) (value & 0xFF);
return result;
}
private byte[] swap4byte(int value) {
byte[] result = new byte[4];
result[0] = (byte) (value >> 24 & 0xFF);
result[1] = (byte) (value >> 16 & 0xFF);
result[2] = (byte) (value >> 8 & 0xFF);
result[3] = (byte) (value & 0xFF);
return result;
}
/**
* midi文件的头部类
*/
class MidiHead {//MIDI文件头
byte[] MidiId = {0x4D, 0x54, 0x68, 0x64};//MIDI文件标志 MThd
byte[] midiInfoLen = {0x00, 0x00, 0x00, 0x06}; //头部信息长度 format + trackNUm + division的长度
byte[] format = {0x00, 0x00}; //存放的格式 0x00 0x00:单音轨,0x00 0x01:多音轨且同步,0x00 02:多音轨但不同步
byte[] trackNum = {0x00, 0x01}; //音轨数目
byte[] division; //指定计数的方法,一种随时间计数(最高位设置为0时),另一种使用制式的时间码(最高位设置为1时)
public MidiHead(int division) {
this.division = swap2byte(division);
}
public int getMidiHeadLength() {
return (MidiId.length + midiInfoLen.length + format.length + trackNum.length + division.length);
}
public byte[] getMidiHeadBytes() {
int length = MidiId.length + midiInfoLen.length + format.length + trackNum.length + division.length;
byte[] result = new byte[length];
int posion = 0;
System.arraycopy(MidiId, 0, result, posion, MidiId.length);
posion += MidiId.length;
System.arraycopy(midiInfoLen, 0, result, posion, midiInfoLen.length);
posion += midiInfoLen.length;
System.arraycopy(format, 0, result, posion, format.length);
posion += format.length;
System.arraycopy(trackNum, 0, result, posion, trackNum.length);
posion += trackNum.length;
System.arraycopy(division, 0, result, posion, division.length);
return result;
}
}
/**
* 音轨的头部类
*/
class TrackHead {//音轨头
byte[] mTrackId = {0x4D, 0x54, 0x72, 0x6B}; //磁道标志 MTrk
// byte[] trackInfoByteNumber; //该轨道的字节数
byte[] mTrackNameInfo;
byte[] mTrackInfo = {0x00, (byte) 0xFF, 0x58, 0x04, 0x00, 0x00, 0x18, 0x08};//节拍信息
byte[] mTrackController1 = {0x00, (byte) 0xB0, 0x0A, 0x40};//控制器信息
byte[] mTrackController2 = {0x00, (byte) 0xB1, 0x0A, 0x40};//控制器信息
byte[] mTrackSpeed = {0x00, (byte) 0xFF, 0x51, 0x03, 0x00, 0x00, 0x00};//节拍速度
public TrackHead(byte[] name, int speed, int numerator, int denominator) {
mTrackNameInfo = new byte[name.length + 4];
mTrackNameInfo[0] = 0x00;
mTrackNameInfo[1] = (byte) 0xFF;
mTrackNameInfo[2] = 0x03;
mTrackNameInfo[3] = (byte) (0xFF & name.length);
System.arraycopy(name, 0, mTrackNameInfo, 4, name.length);
mTrackInfo[4] = (byte) (0xFF & numerator);
switch (denominator) {
case 2:
mTrackInfo[5] = 1;
break;
case 4:
mTrackInfo[5] = 2;
break;
case 8:
mTrackInfo[5] = 3;
break;
default:
mTrackInfo[5] = 2;
break;
}
int quarterTrackSpeed = (int) (1000000.0 * 60.0 / speed);
byte[] quarterSpeedBytes = swap3byte(quarterTrackSpeed);
System.arraycopy(quarterSpeedBytes, 0, mTrackSpeed, 4, 3);
}
public int getTrackHeadLength() {
int totalLenght = 4 + 4
+ mTrackNameInfo.length
+ mTrackInfo.length
+ mTrackController1.length
+ mTrackController2.length
+ mTrackSpeed.length;
return totalLenght;
}
public byte[] getTrackHeadBytes(int eventLen) {
int trackInfoLen = eventLen
+ mTrackNameInfo.length
+ mTrackInfo.length
+ mTrackController1.length
+ mTrackController2.length
+ mTrackSpeed.length;
byte[] trackInfoByteNumber = swap4byte(trackInfoLen);//该轨道的字节数
int totalLenght = 4 + 4
+ mTrackNameInfo.length
+ mTrackInfo.length
+ mTrackController1.length
+ mTrackController2.length
+ mTrackSpeed.length;
byte[] result = new byte[totalLenght];
int posion = 0;
System.arraycopy(mTrackId, 0, result, posion, 4);
posion += 4;
System.arraycopy(trackInfoByteNumber, 0, result, posion, 4);
posion += 4;
System.arraycopy(mTrackNameInfo, 0, result, posion, mTrackNameInfo.length);
posion += mTrackNameInfo.length;
System.arraycopy(mTrackInfo, 0, result, posion, mTrackInfo.length);
posion += mTrackInfo.length;
System.arraycopy(mTrackController1, 0, result, posion, mTrackController1.length);
posion += mTrackController1.length;
System.arraycopy(mTrackController2, 0, result, posion, mTrackController2.length);
posion += mTrackController2.length;
System.arraycopy(mTrackSpeed, 0, result, posion, mTrackSpeed.length);
return result;
}
}
/**********************************
* 动态存储空间类
**********************************/
class EventStorageSpace {
private int mEventlength = 0;
private int mHeadLength = 0;
private byte[] mStorageBuffer;
private List<StorageUnit> mStorageUnitList = new LinkedList<StorageUnit>();
/**
* headLen:头部空出的空间长度,为存放其它内容
*/
public EventStorageSpace(int headLen) {
mHeadLength = headLen;
}
/**
* 写入事件
*/
public void writeEvent(byte[] buffer, int len) {
if (buffer == null) return;
mEventlength += len;
if (buffer == null || len <= 0) return;
//第一次写走这个单元
if (mStorageUnitList.size() <= 0) {
StorageUnit newUnit = new StorageUnit();
newUnit.writeBuffer(buffer, len);
mStorageUnitList.add(newUnit);
} else {
//取出列表中的最后一个单元
StorageUnit oldUnit = mStorageUnitList.get(mStorageUnitList.size() - 1);
//查看这个单元是否可以再写了
if (oldUnit.isCanInsert(len)) {
oldUnit.writeBuffer(buffer, len);
mStorageUnitList.set(mStorageUnitList.size() - 1, oldUnit);
} else {
StorageUnit newUnit = new StorageUnit();
newUnit.writeBuffer(buffer, len);
mStorageUnitList.add(newUnit);
}
}
}
/**
* 获取存储空间的内容句柄
*/
public byte[] getTotalBuffer() {
if (mStorageBuffer != null) mStorageBuffer = null;
if (mEventlength == 0) {
writeEvent(mFinishBytes, mFinishBytes.length);
}
mStorageBuffer = new byte[mHeadLength + mEventlength];
int index = mHeadLength;
for (int i = 0; i < mStorageUnitList.size(); i++) {
StorageUnit unit = mStorageUnitList.get(i);
int unitLen = unit.getLength();
byte[] unitBuffer = unit.getUnitBuffers();
System.arraycopy(unitBuffer, 0, mStorageBuffer, index, unitLen);
index += unitLen;
}
return mStorageBuffer;
}
/**
* 获取存储空间的总长度
*/
public int getTotalLength() {
return (mHeadLength + mEventlength);
}
/**
* 获取写入事件的总长度
*/
public int getEventLength() {
return mEventlength;
}
/**********************************************
* 动态存申请的一个小单元,容量是 TOTAL_LENGTH 个字节
**********************************************/
class StorageUnit {
final int TOTAL_LENGTH = 1024;
int posion = 0;
byte[] mBuffer = new byte[TOTAL_LENGTH];
/**
* 判断是否可以写入
*/
public boolean isCanInsert(int len) {
return (TOTAL_LENGTH - posion >= len);
}
/**
* 写内容
*/
public void writeBuffer(byte[] buffer, int len) {
if (buffer == null) return;
for (int i = 0; i < len; i++) {
mBuffer[posion++] = buffer[i];
}
}
/**
* 获取本单元的实际长度
*/
public int getLength() {
return posion;
}
/**
* 获取本单元的空间句柄
*/
public byte[] getUnitBuffers() {
return mBuffer;
}
}
}
}