这学期,笔者在制作一款音乐应用中需要用到多种乐器,纠结于程序包的大小后选择了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来编辑数据。 代码如下:
import java.io.FileOutputStream; import java.io.IOException; // We are working with 32 ticks to the crotchet. So // all the other note lengths can be derived from this // basic figure. Note that the longest note we can // represent with this code is one tick short of a // two semibreves (i.e., 8 crotchets) // Standard MIDI file header, for one-track file // 4D, 54... are just magic numbers to identify the headers // Note that because we're only writing one track, we // can for simplicity combine the file and track headers private int header[] = new int[] 0x4d, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, // multi-track format synchronous 0x00, 0x78, // 16 ticks per quarter private int channelHeader[] = new int[] private int channelFooter[] = new int[] // A MIDI event to set the tempo(速度) private int tempoEvent[] = new int[] 0x0F, 0x42, 0x40 // Default 1 million usec per crotchet // A MIDI event to set the key signature. This is irrelent to // playback, but necessary for editing applications(升降调) private int keySigEvent[] = new int[] // A MIDI event to set the time signature. This is irrelent to // playback, but necessary for editing applications(节拍) private int timeSigEvent[] = new int[] 0x02, // denominator (2==4, because it's a power of 2) 0x30, // ticks per click (not used) 0x08 // 32nd notes per crotchet private Vector<MidiChannel> mChannels = new Vector<MidiChannel>(); /** Construct a new MidiFile with an empty playback event list */ public MidiFile(float tempo/*secPerCrotchet,default 1.0f*/,int keySig/*default 0, must between -7 to 7*/, int timeSigNumerator/*default 4*/,int timeSigDenominator/*default 2, exponent of 2*/) if (Math.abs(tempo-1.0f) > 0.001f) int microSec = (int)(tempo * 1000000); tempoEvent[6] = microSec % 0x100; tempoEvent[5] = ((int)(microSec / 0x100)) % 0x100; tempoEvent[4] = microSec / 0x10000; timeSigEvent[4] = timeSigNumerator; timeSigEvent[5] = timeSigDenominator; public void setBaseTicks(int ticks) byte[] bytes = intToByteArray(ticks); public int getBaseTicks() return (header[12]* 0x100 + header[13]); public void setTempo(float tempo) int microSec = (int)(tempo * 1000000); tempoEvent[6] = microSec % 0x100; tempoEvent[5] = (microSec / 0x100) % 0x100; tempoEvent[4] = microSec / 0x10000; return (tempoEvent[4] * 0x10000 + tempoEvent[5] * 0x100 + tempoEvent[6]) / 1000000.0f; public void setKeySig(int keySig) public void setTimeSig(int timeSigNumerator,int timeSigDenominator) timeSigEvent[4] = timeSigNumerator; timeSigEvent[5] = timeSigDenominator; public void addChannel(MidiChannel channel) /** Write the stored MIDI events to a file */ public void writeToFile (String filename) throws IOException FileOutputStream fos = new FileOutputStream (filename); header[11] = mChannels.size() + 1; fos.write (intArrayToByteArray (header)); fos.write(intArrayToByteArray(channelHeader)); // Calculate the amount of track data // _Do_ include the footer but _do not_ include the track header int size = tempoEvent.length + keySigEvent.length + timeSigEvent.length + channelFooter.length; fos.write(intToByteArray(size)); // Write the standard metadata — tempo, etc // At present, tempo is stuck at crotchet=60 fos.write (intArrayToByteArray (tempoEvent)); fos.write (intArrayToByteArray (keySigEvent)); fos.write (intArrayToByteArray (timeSigEvent)); fos.write(intArrayToByteArray(channelFooter)); for (int i =0; i<mChannels.size();++i) fos.write(intArrayToByteArray(channelHeader)); size = channelFooter.length; MidiChannel channel = mChannels.elementAt(i); for (int j = 0; j < channel.playEvents.size(); ++j) size += channel.playEvents.elementAt(j).length; fos.write(intToByteArray(size)); // Write out the note, etc., events for (int j = 0; j < channel.playEvents.size();++j) fos.write (intArrayToByteArray (channel.playEvents.elementAt(j))); // Write the footer and close fos.write (intArrayToByteArray (channelFooter)); /** Convert an array of integers which are assumed to contain unsigned bytes into an array of bytes */ protected static byte[] intArrayToByteArray (int[] ints) byte[] out = new byte[ints.length]; for (int i = 0; i < l; i++) protected static byte[] intToByteArray(int i) // Write out the track data size in big-endian format // Note that this math is only valid for up to 64k of data // (but that's a lot of notes) byte[] out = new byte[4]; out[2] = (byte)(i / 256); out[3] = (byte)(i - ((int)out[2] * 256));
private int mChannelIndex = 1; // The collection of events to play, in time order public Vector<int[]> playEvents = new Vector<int[]>(); public MidiChannel(int channelIndex/*default 1, between 00 to FF*/) mChannelIndex = channelIndex; public void setChannelIndex(int channelIndex) mChannelIndex = channelIndex; /** Store a program-change event at current position */ public void progChange (int prog/*0x00-0x7f*/) data[1] = 0xC0 + mChannelIndex; /** Store a note-on event */ public void noteOn (int delta, int note, int velocity) data[1] = 0x90 + mChannelIndex; /** Store a note-off event */ public void noteOff (int delta, int note) data[1] = 0x80 + mChannelIndex; /** Store a note-on event followed by a note-off event a note length later. There is no delta value — the note is assumed to follow the previous one with no gap. */ public void noteOnOffNow (int duration, int note, int velocity) noteOn (0, note, velocity); noteOff (duration, note); public void noteSequenceFixedVelocity (int[] sequence, int velocity) boolean lastWasRest = false; for (int i = 0; i < sequence.length; i += 2) int duration = sequence[i + 1]; noteOn (restDelta, note, velocity); noteOff (duration, note); noteOn (0, note, velocity); noteOff (duration, note);
下面是生成所有MIDI乐器的MIDI音乐示例:
public static void main (String[] args) throws Exception String[] instruments = new String[] 'Synth_Effect_soundtrack', 'Synth_Effect_atmosphere', 'Synth_Effect_brightness', MidiFile mf = new MidiFile(); mf.noteOnOffNow(MINIM, j, 127); mf.writeToFile (instruments[i]+'_'+String.valueOf(j)+'.mid');
以上文章仅供参考,如有纰漏烦请指正,交流请邮件:fuzhiyang0123@yahoo.com.cn
|