分享

Android MIDI音乐播放/生成相关总结

 洪明轩 2019-09-18

这学期,笔者在制作一款音乐应用中需要用到多种乐器,纠结于程序包的大小后选择了MIDI音乐,查阅大量资料和博客后实现成功。讲制作过程中的理解分享如下,如有纰漏,望包涵并纠正。

一、Android平台音乐媒体以及MIDI音乐现状

目前Android平台使用的音乐媒体格式多为MP3、OGG、WAV等常用格式,,常用铃声格式为MP3、WMA格式音频文件。WAV为源声音文件,无损,音质高,文件大。MP3、OGG等均为压缩音频,需要特定的编解码器支持。

这是Android支持的媒体格式列表链接:

http://developer./guide/appendix/media-formats.html

虽然MP3、OGG等常用音乐格式可以满足大部分应用和游戏开发,但如果程序中涉及大量的音频文件,使用这些格式后的程序包文件肯定是非常大的,比如涉及多种乐器的音乐程序和大型游戏。其实还有另外一种更加常用、更加强大的音乐媒体格式,那就是MIDI音乐。关于MIDI音乐相关介绍,请各位看官自行搜索。个人觉得百度百科解释的非常好,链接如下: http://baike.baidu.com/view/7969.htm

MIDI仅仅是一个通信标准,它是由电子乐器制造商们建立起来的,用以确定电脑音乐程序、合成器和其他电子音响的设备互相交换信息与控制信号的方法。MIDI系统实际就是一个作曲、配器、电子模拟的演奏系统。从一个MIDI设备转送到另一个MIDI设备上去的数据就是MIDI信息。MIDI数据不是数字的音频波形,而是音乐代码或称电子乐谱。也就是说MIDI数据中没有声音数据,有的只是电子乐器的控制信息,需要借用电脑或者手机的声卡或者其他专业声卡来发声,音质肯定与声卡的质量相关。
MIDI音乐支持其实比MP3、OGG等更好,甚至Nokia仅能打电话和发短信不能播放MP3的低端机都支持MIDI,但像iPhone高端机却不支持。
Android平台对MIDI的支持情况其实也不好。具体请参考如下链接:
http://home./home.php?uid=5886&do=blog&id=15&mod=space
关于JetPlayer,平台下的Samples有个程序范例JetBoy。同时参考如下文章:
JetBoy游戏深入解析(中)--JetPlayer类解析:http://www.mo/?p=1333

二、MIDI文件格式分析
MIDI文件中不保存声音数据,只保存乐器和合成器等信息。
MIDI文件格式请参考下面几篇文章,在制作过程中肯定是要经常用到的。
MIDI音乐格式分析--理论篇:http://www./modulearticle-detailview-901.htm
MIDI音乐格式分析--实践篇:http://www./modulearticle-detailview-902.htm
MIDI音乐格式分析--附件篇:http://www./modulearticle-detailview-903.htm
或者可以参考MIDI的官方文档,会有详细介绍。
三、Android MIDI音乐播放与生成
从官方媒体格式列表中也可以看到Android的MediaPlayer支持MIDI文件的直接播放,具体播放过程与其他音乐文件无异,就不给出代码了。
但是有时候我们希望能对音频数据进行编辑合成,而MIDI音乐不保存音频数据,这时候就需要我们自己来写MIDI乐器与合成器的控制信息了,而且都是16进制数据。我们需要在程序中自己控制生成,当然你也可以自己用UltraEdit来编辑数据。
代码如下:
  1. import java.io.FileOutputStream;
  2. import java.io.IOException;
  3. import java.util.Vector;
  4. public class MidiFile
  5. {
  6. // Note lengths
  7. // We are working with 32 ticks to the crotchet. So
  8. // all the other note lengths can be derived from this
  9. // basic figure. Note that the longest note we can
  10. // represent with this code is one tick short of a
  11. // two semibreves (i.e., 8 crotchets)
  12. // Standard MIDI file header, for one-track file
  13. // 4D, 54... are just magic numbers to identify the headers
  14. // Note that because we're only writing one track, we
  15. // can for simplicity combine the file and track headers
  16. private int header[] = new int[]
  17. {
  18. 0x4d, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06,
  19. 0x00, 0x01, // multi-track format synchronous
  20. 0x00, 0x10, // two track
  21. 0x00, 0x78, // 16 ticks per quarter
  22. };
  23. private int channelHeader[] = new int[]
  24. {
  25. 0x4d, 0x54, 0x72, 0x6B
  26. };
  27. // Standard footer
  28. private int channelFooter[] = new int[]
  29. {
  30. 0x01, 0xFF, 0x2F, 0x00
  31. };
  32. // A MIDI event to set the tempo(速度)
  33. private int tempoEvent[] = new int[]
  34. {
  35. 0x00, 0xFF, 0x51, 0x03,
  36. 0x0F, 0x42, 0x40 // Default 1 million usec per crotchet
  37. };
  38. // A MIDI event to set the key signature. This is irrelent to
  39. // playback, but necessary for editing applications(升降调)
  40. private int keySigEvent[] = new int[]
  41. {
  42. 0x00, 0xFF, 0x59, 0x02,
  43. 0x00, // C
  44. 0x00 // major
  45. };
  46. // A MIDI event to set the time signature. This is irrelent to
  47. // playback, but necessary for editing applications(节拍)
  48. private int timeSigEvent[] = new int[]
  49. {
  50. 0x00, 0xFF, 0x58, 0x04,
  51. 0x04, // numerator
  52. 0x02, // denominator (2==4, because it's a power of 2)
  53. 0x30, // ticks per click (not used)
  54. 0x08 // 32nd notes per crotchet
  55. };
  56. private Vector<MidiChannel> mChannels = new Vector<MidiChannel>();
  57. public MidiFile()
  58. {}
  59. /** Construct a new MidiFile with an empty playback event list */
  60. public MidiFile(float tempo/*secPerCrotchet,default 1.0f*/,int keySig/*default 0, must between -7 to 7*/,
  61. int timeSigNumerator/*default 4*/,int timeSigDenominator/*default 2, exponent of 2*/)
  62. {
  63. if (Math.abs(tempo-1.0f) > 0.001f)
  64. {
  65. int microSec = (int)(tempo * 1000000);
  66. tempoEvent[6] = microSec % 0x100;
  67. tempoEvent[5] = ((int)(microSec / 0x100)) % 0x100;
  68. tempoEvent[4] = microSec / 0x10000;
  69. }
  70. keySigEvent[4] = keySig;
  71. timeSigEvent[4] = timeSigNumerator;
  72. timeSigEvent[5] = timeSigDenominator;
  73. }
  74. public void setBaseTicks(int ticks)
  75. {
  76. byte[] bytes = intToByteArray(ticks);
  77. header[12] = bytes[2];
  78. header[13] = bytes[3];
  79. }
  80. public int getBaseTicks()
  81. {
  82. return (header[12]* 0x100 + header[13]);
  83. }
  84. public void setTempo(float tempo)
  85. {
  86. int microSec = (int)(tempo * 1000000);
  87. tempoEvent[6] = microSec % 0x100;
  88. tempoEvent[5] = (microSec / 0x100) % 0x100;
  89. tempoEvent[4] = microSec / 0x10000;
  90. }
  91. public float getTempo()
  92. {
  93. return (tempoEvent[4] * 0x10000 + tempoEvent[5] * 0x100 + tempoEvent[6]) / 1000000.0f;
  94. }
  95. public void setKeySig(int keySig)
  96. {
  97. keySigEvent[4] = keySig;
  98. }
  99. public int getKeySig()
  100. {
  101. return keySigEvent[4];
  102. }
  103. public void setTimeSig(int timeSigNumerator,int timeSigDenominator)
  104. {
  105. timeSigEvent[4] = timeSigNumerator;
  106. timeSigEvent[5] = timeSigDenominator;
  107. }
  108. public int getTimeSig()
  109. {
  110. return timeSigEvent[4];
  111. }
  112. public void addChannel(MidiChannel channel)
  113. {
  114. mChannels.add(channel);
  115. }
  116. /** Write the stored MIDI events to a file */
  117. public void writeToFile (String filename) throws IOException
  118. {
  119. FileOutputStream fos = new FileOutputStream (filename);
  120. header[11] = mChannels.size() + 1;
  121. fos.write (intArrayToByteArray (header));
  122. fos.write(intArrayToByteArray(channelHeader));
  123. // Calculate the amount of track data
  124. // _Do_ include the footer but _do not_ include the track header
  125. int size = tempoEvent.length + keySigEvent.length + timeSigEvent.length + channelFooter.length;
  126. fos.write(intToByteArray(size));
  127. // Write the standard metadata — tempo, etc
  128. // At present, tempo is stuck at crotchet=60
  129. fos.write (intArrayToByteArray (tempoEvent));
  130. fos.write (intArrayToByteArray (keySigEvent));
  131. fos.write (intArrayToByteArray (timeSigEvent));
  132. //end global track
  133. fos.write(intArrayToByteArray(channelFooter));
  134. for (int i =0; i<mChannels.size();++i)
  135. {
  136. fos.write(intArrayToByteArray(channelHeader));
  137. size = channelFooter.length;
  138. MidiChannel channel = mChannels.elementAt(i);
  139. for (int j = 0; j < channel.playEvents.size(); ++j)
  140. size += channel.playEvents.elementAt(j).length;
  141. fos.write(intToByteArray(size));
  142. // Write out the note, etc., events
  143. for (int j = 0; j < channel.playEvents.size();++j)
  144. {
  145. fos.write (intArrayToByteArray (channel.playEvents.elementAt(j)));
  146. }
  147. // Write the footer and close
  148. fos.write (intArrayToByteArray (channelFooter));
  149. }
  150. fos.close();
  151. }
  152. public void release()
  153. {
  154. mChannels.clear();
  155. }
  156. /** Convert an array of integers which are assumed to contain
  157. unsigned bytes into an array of bytes */
  158. protected static byte[] intArrayToByteArray (int[] ints)
  159. {
  160. int l = ints.length;
  161. byte[] out = new byte[ints.length];
  162. for (int i = 0; i < l; i++)
  163. {
  164. out[i] = (byte) ints[i];
  165. }
  166. return out;
  167. }
  168. protected static byte[] intToByteArray(int i)
  169. {
  170. // Write out the track data size in big-endian format
  171. // Note that this math is only valid for up to 64k of data
  172. // (but that's a lot of notes)
  173. byte[] out = new byte[4];
  174. out[0] = 0;
  175. out[1] = 0;
  176. out[2] = (byte)(i / 256);
  177. out[3] = (byte)(i - ((int)out[2] * 256));
  178. return out;
  179. }
  180. }
  1. import java.util.Vector;
  2. public class MidiChannel
  3. {
  4. private int mChannelIndex = 1;
  5. // The collection of events to play, in time order
  6. public Vector<int[]> playEvents = new Vector<int[]>();
  7. public MidiChannel()
  8. {}
  9. public MidiChannel(int channelIndex/*default 1, between 00 to FF*/)
  10. {
  11. mChannelIndex = channelIndex;
  12. }
  13. public void setChannelIndex(int channelIndex)
  14. {
  15. mChannelIndex = channelIndex;
  16. }
  17. /** Store a program-change event at current position */
  18. public void progChange (int prog/*0x00-0x7f*/)
  19. {
  20. int[] data = new int[3];
  21. data[0] = 0;
  22. data[1] = 0xC0 + mChannelIndex;
  23. data[2] = prog;
  24. playEvents.add(data);
  25. }
  26. /** Store a note-on event */
  27. public void noteOn (int delta, int note, int velocity)
  28. {
  29. int[] data = new int[4];
  30. data[0] = delta;
  31. data[1] = 0x90 + mChannelIndex;
  32. data[2] = note;
  33. data[3] = velocity;
  34. playEvents.add (data);
  35. }
  36. /** Store a note-off event */
  37. public void noteOff (int delta, int note)
  38. {
  39. int[] data = new int[4];
  40. data[0] = delta;
  41. data[1] = 0x80 + mChannelIndex;
  42. data[2] = note;
  43. data[3] = 0;
  44. playEvents.add (data);
  45. }
  46. /** Store a note-on event followed by a note-off event a note length
  47. later. There is no delta value — the note is assumed to
  48. follow the previous one with no gap. */
  49. public void noteOnOffNow (int duration, int note, int velocity)
  50. {
  51. noteOn (0, note, velocity);
  52. noteOff (duration, note);
  53. }
  54. public void noteSequenceFixedVelocity (int[] sequence, int velocity)
  55. {
  56. boolean lastWasRest = false;
  57. int restDelta = 0;
  58. for (int i = 0; i < sequence.length; i += 2)
  59. {
  60. int note = sequence[i];
  61. int duration = sequence[i + 1];
  62. if (note < 0)
  63. {
  64. // This is a rest
  65. restDelta += duration;
  66. lastWasRest = true;
  67. }
  68. else
  69. {
  70. // A note, not a rest
  71. if (lastWasRest)
  72. {
  73. noteOn (restDelta, note, velocity);
  74. noteOff (duration, note);
  75. }
  76. else
  77. {
  78. noteOn (0, note, velocity);
  79. noteOff (duration, note);
  80. }
  81. restDelta = 0;
  82. lastWasRest = false;
  83. }
  84. }
  85. }
  86. }
下面是生成所有MIDI乐器的MIDI音乐示例:
  1. public static void main (String[] args) throws Exception
  2. {
  3. String[] instruments = new String[]
  4. {
  5. 'Acoustic_Grand_Piano',
  6. 'Bright_Acoustic_Piano',
  7. 'Electric_Grand_Piano',
  8. 'Honky_Tonk_Piano',
  9. 'Rhodes_Piano',
  10. 'Chorused_Piano',
  11. 'Harpsichord',
  12. 'Clavine',
  13. 'Celesta',
  14. 'Glockenspiel',
  15. 'Music_Box',
  16. 'Vibraphone',
  17. 'Marimba',
  18. 'Xylophone',
  19. 'Tubular_Bells',
  20. 'Dulcimer',
  21. 'Hammond_Organ',
  22. 'Percussive_Organ',
  23. 'Rock_Organ',
  24. 'Church_Organ',
  25. 'Reed_Organ',
  26. 'Accordion',
  27. 'Harmonica',
  28. 'Tango_Accordian',
  29. 'Acoustic_Guitar_nylon',
  30. 'Acoustic_Guitar_steel',
  31. 'Electric_Guitar_jazz',
  32. 'Electric_Guitar_clean',
  33. 'Electric_Guitar_muted',
  34. 'Overdriven_Guitar',
  35. 'Distortion_Guitar',
  36. 'Guitar_Harmonics',
  37. 'Acoustic_Bass',
  38. 'Electric_Bass_finger',
  39. 'Electric_Bass_pick',
  40. 'Fretless_Bass',
  41. 'Slap_Bass_1',
  42. 'Slap_Bass_2',
  43. 'Synth_Bass_1',
  44. 'Synth_Bass_2',
  45. 'Violin',
  46. 'Viola',
  47. 'Cello',
  48. 'Contrabass',
  49. 'Tremolo_Strings',
  50. 'Pizzicato_Strings',
  51. 'Orchestral_Harp',
  52. 'Timpani',
  53. 'String_Ensemble_1',
  54. 'String_Ensemble_2',
  55. 'SynthStrings_1',
  56. 'SynthStrings_2',
  57. 'Choir_Aahs',
  58. 'Voice_Oohs',
  59. 'Synth_Voice',
  60. 'Orchestra_Hit',
  61. 'Trumpet',
  62. 'Trombone',
  63. 'Tuba',
  64. 'Muted_Trumpet',
  65. 'French_Horn',
  66. 'Brass_Section',
  67. 'Synth_Brass_1',
  68. 'Synth_Brass_2',
  69. 'Soprano_Sax',
  70. 'Alto_Sax',
  71. 'Tenor_Sax',
  72. 'Baritone_Sax',
  73. 'Oboe',
  74. 'English_Horn',
  75. 'Bassoon',
  76. 'Clarinet',
  77. 'Piccolo',
  78. 'Flute',
  79. 'Recorder',
  80. 'Pan_Flute',
  81. 'Bottle_Blow',
  82. 'Skakuhachi',
  83. 'Whistle',
  84. 'Ocarina',
  85. 'Synth_Lead_square',
  86. 'Synth_Lead_sawtooth',
  87. 'Synth_Lead_calliope',
  88. 'Synth_Lead_chiff',
  89. 'Synth_Lead_charang',
  90. 'Synth_Lead_voice',
  91. 'Synth_Lead_fifths',
  92. 'Synth_Lead_bass_lead',
  93. 'Synth_Pad_new_age',
  94. 'Synth_Pad_warm',
  95. 'Synth_Pad_polysynth',
  96. 'Synth_Pad_choir',
  97. 'Synth_Pad_bowed',
  98. 'Synth_Pad_metallic',
  99. 'Synth_Pad_halo',
  100. 'Synth_Pad_sweep',
  101. 'Synth_Effect_rain',
  102. 'Synth_Effect_soundtrack',
  103. 'Synth_Effect_crystal',
  104. 'Synth_Effect_atmosphere',
  105. 'Synth_Effect_brightness',
  106. 'Synth_Effect_goblins',
  107. 'Synth_Effect_echoes',
  108. 'Synth_Effect_sci_fi',
  109. 'Sitar',
  110. 'Banjo',
  111. 'Shamisen',
  112. 'Koto',
  113. 'Kalimba',
  114. 'Bagpipe',
  115. 'Fiddle',
  116. 'Shanai',
  117. 'Tinkle_Bell',
  118. 'Agogo',
  119. 'Steel_Drums',
  120. 'Woodblock',
  121. 'Taiko_Drum',
  122. 'Melodic_Tom',
  123. 'Synth_Drum',
  124. 'Reverse_Cymbal',
  125. 'Guitar_Fret_Noise',
  126. 'Breath_Noise',
  127. 'Seashore',
  128. 'Bird_Tweet',
  129. 'Telephone_Ring',
  130. 'Helicopter',
  131. 'Applause',
  132. 'Gunshot'
  133. };
  134. int[] progs = new int[]
  135. {
  136. 0x00,
  137. 0x18,
  138. 0x28,
  139. 0x29,
  140. 0x2a,
  141. 0x21,
  142. 0x76,
  143. 0x41
  144. };
  145. for (int i=0;i<=8;++i)
  146. {
  147. for (int j=0;j<=127;++j)
  148. {
  149. MidiFile mf = new MidiFile();
  150. mf.progChange(progs[i]);
  151. mf.noteOnOffNow(MINIM, j, 127);
  152. mf.writeToFile (instruments[i]+'_'+String.valueOf(j)+'.mid');
  153. }
  154. }
  155. }
以上文章仅供参考,如有纰漏烦请指正,交流请邮件:fuzhiyang0123@yahoo.com.cn

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多