Android外接midi设备的录音
本人借鉴了两篇文章:
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));elsebuffer[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++;}//除第一个字节,其它自己都加上0x80for (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文件标志 MThdbyte[] 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;}}}}
Android外接midi设备的录音相关推荐
- Android 读取外接储存设备的数据(如挂载的U盘,SD卡等)
本篇文章,将围绕以下几点来讲解: 1:OTG是什么? 2:Android手机和一些Android系统的TV盒子对OTG的支持情况? 3:如何得知外接储存设备的插入和拔出的广播事件? 4:得到插入广播后 ...
- Android 外接设备获取驱动和获取申请权限
目录 前言 一.UsbManager 是什么? 二.使用步骤 1.获取UsbManager 2.获取设备驱动列表 3.和厂家或三方获取接入的驱动的参数 4.检查权限.申请权限 5.源码示例 总结 前言 ...
- linux录音设备权限,Android 6.0 之前的录音权限问题
Android 6.0 之前的录音权限问题 Android,权限,录音 2018.08.13 Android 6.0 提出了新的权限管理机制.而在 6.0 之前,各个厂商也搞了一些自己的权限管理机制, ...
- cubase怎么添加midi设备_WIDI MASTER:5针无线蓝牙MIDI适配器
WIDI Master 是CME公司在众创项目后根据广大音乐人的需求进行设计并生产的一款产品.CME已经在无线MIDI解决方案上工作了15年以上.在推出Xkey Air(2015年)之后,他们收到了许 ...
- pos 机 gd32f103 midi设备
1/芯片本身有usb 直接 初始化成 usb midi 设备,然后将 io 数据传输到串口就ok 引用连接:https://blog.csdn.net/weixin_41082557/article/ ...
- 用树莓派做MIDI HOST,给合成器外接MIDI键盘
最近买了几台迷你合成器,包括KORG的volca系列和Roland的T-8.KORG的volca系列迷你合成器,都有midi输入接口,可以外接midi键盘.但midi输入接口是传统的5针midi接口, ...
- 安卓Android开发:使用AudioRecord录音、将录音保存为wav文件、使用AudioTrack保存录音
一.使用AudioRrecord录音 1.1声明 首先需要声明一个AudioRecord类的实例.之所以需要事先声明,是因为在本例中,录音的启动和结束被封装在两个不同的方法里.而通常来讲," ...
- Android音视频API(android.media.midi):概览
提供通过USB,蓝牙LE和虚拟(应用程序间)传输使用标准MIDI事件协议发送和接收消息的类. 一.概要 Android MIDI包允许用户: 1.将MIDI键盘连接到Android以播放合成器或驱动音 ...
- 微软:外接 USB 设备或 SD 卡时将无法更新 Windows 1903
近日,微软发布了一篇支持文档,提醒 Window 10 用户,如果使用外部 USB 存储设备或 SD 卡,即将发布的 2019 年 5 月更新(即 1903 版)可能无法正确安装. 该公告指出,受影响 ...
最新文章
- 概率链接nbu 2416 奇怪的散步
- jQuery 内容文本值|| 案例:购物车案例模块-增减商品数量 || 案例:购物车案例模块-修改商品小计
- 在Linux环境下mysql的root密码忘记解决方法(三种)
- Python中关于使用正则表达式相关的部分笔记
- linux卸载git,并且安装新版本git
- oracle adf_Fn函数来构建Oracle ADF应用程序
- 我实在不懂Python的Asyncio
- win10 + 独显 + Anaconda3 + tensorflow_gpu1.13 安装教程(跑bert模型)
- 用十万级数据进行讲解MySQL索引基础
- html5新加的元素,HTML5新增元素
- EXP-00091: Exporting questionable statistics
- [GIS教程] 5.2 空间数据管理 | SDE空间数据引擎
- 【分布式开发】之 CAP 原则
- 游戏建模师自学3D建模有哪些教材?自学难吗?
- 苹果奖学金获得者:我的自学 iOS 开发历程
- CLEAR: Contrastive Learning for Sentence Representation
- 根据正规文法构建状态转换图
- 自动驾驶车载相机rosenberger接口防呆设计
- Android - 指纹识别API示例
- 数据泄露事件频发,病毒入侵猖獗,装上MCK就能杜绝
热门文章
- //毫米到英寸,单位换算
- 从REINFORCE到PPO,看Policy Gradient的前世今生
- # ffmpeg 将多幅图片压缩成视频 h264 avi
- VS2017使用gtest
- 7. IIS短文件/文件夹漏洞(汇总整理)
- Excel按不同的字体颜色对数据区域分类汇总求和
- 【算法基础】DFS深度优先算法 —— AcWing 843. n-皇后问题 AcWing 842. 排列数字
- 或许你就是那个背锅侠【多图】
- 逍遥android模拟器设置,逍遥安卓模拟器更改分辨率的具体操作方法
- 【计导非课系列】 第五节 二进制 进制计算 编码