|
1 /* |
|
2 * Copyright (c) 2002 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: This class represents midi sequence |
|
15 * |
|
16 */ |
|
17 |
|
18 |
|
19 package com.nokia.microedition.media.tone; |
|
20 |
|
21 import javax.microedition.media.control.ToneControl; |
|
22 import java.io.ByteArrayOutputStream; |
|
23 import java.io.ByteArrayInputStream; |
|
24 import java.io.IOException; |
|
25 |
|
26 /** |
|
27 * This class represents midi sequence |
|
28 */ |
|
29 public class MidiSequence |
|
30 { |
|
31 // CONSTANTS |
|
32 |
|
33 /* Value of minute expressed as microseconds */ |
|
34 private static final int MINUTE_AS_MICROSECONDS = 60000000; |
|
35 |
|
36 /* MIDI events track stream granularity */ |
|
37 private static final int MIDI_EVENTS_TRACK_GRANULARITY = 100; |
|
38 |
|
39 // MIDI constants |
|
40 |
|
41 /* Note value used for silence events. This will not be audible */ |
|
42 public static final byte MIDI_SILENCE_NOTE = 0; |
|
43 |
|
44 // MIDI file constants |
|
45 |
|
46 /* Maximum length of midi sequence events. After this has been |
|
47 met, no new events are accepted by writeMidiEvent. This is not |
|
48 a hard limit anyway (may be exceeded by the last written event), |
|
49 so it's only here to guard memory use and possible infinite |
|
50 sequence */ |
|
51 |
|
52 private static final int MIDI_EVENTS_MAX_BYTE_COUNT = 32768; |
|
53 |
|
54 /* Length of MIDI file header. This includes following: |
|
55 MThd block id, MThd length, midi format, MTrk chunk amount and PPQN */ |
|
56 private static final byte FILE_HEADER_LENGTH = 14; |
|
57 |
|
58 /* Length of MTrk block. This includes: MTrk block id, MTrk length */ |
|
59 private static final byte MTRK_HEADER_LENGTH = 8; |
|
60 |
|
61 /* Length of MIDI track header. This includes: |
|
62 tempo change, tempo value, program change */ |
|
63 private static final int TRACK_HEADER_LENGTH = 10; |
|
64 |
|
65 /* Length of MIDI track trailer */ |
|
66 private static final int TRACK_TRAILER_LENGTH = 4; |
|
67 |
|
68 // MIDI file header constants |
|
69 |
|
70 /* Header block for MThd */ |
|
71 private static final byte[] MIDI_HEADER_MTHD = |
|
72 { 0x4D, 0x54, 0x68, 0x64 }; |
|
73 |
|
74 /* Header block for MThd block length; value is 6 */ |
|
75 private static final byte[] MIDI_HEADER_MTHD_LENGTH = |
|
76 { 0x00, 0x00, 0x00, 0x06 }; |
|
77 |
|
78 /* Header block for used MIDI format; format is 0 */ |
|
79 private static final byte[] MIDI_HEADER_MIDI_FORMAT = |
|
80 { 0x00, 0x00 }; |
|
81 |
|
82 /* Header block for amount of MTrk blocks used */ |
|
83 private static final byte[] MIDI_HEADER_MTRK_CHUNK_AMOUNT = |
|
84 { 0x00, 0x01 }; |
|
85 |
|
86 /* Value for first byte in PPQN block in midi file header */ |
|
87 private static final byte MIDI_HEADER_PPQN_FIRST_BYTE = 0x00; |
|
88 |
|
89 // MIDI track constants |
|
90 |
|
91 /* Header block for MTrk */ |
|
92 private static final byte[] MIDI_HEADER_MTRK = |
|
93 { 0x4D, 0x54, 0x72, 0x6B }; |
|
94 |
|
95 /* Tempo change command. Includes delta time ( 0x00 ) |
|
96 and command (0xFF5103) */ |
|
97 private static final byte[] TRACK_HEADER_TEMPO_CHANGE = |
|
98 { 0x00, (byte)0xFF, 0x51, 0x03 }; |
|
99 |
|
100 /* Track end meta event */ |
|
101 private static final byte[] TRACK_TRAILER = |
|
102 { 0x00, (byte)0xFF, 0x2F, 0x00 }; |
|
103 |
|
104 /* Length of single midi event without the variable length |
|
105 delta time */ |
|
106 private static final int MIDI_EVENT_COMMAND_LENGTH = 3; |
|
107 |
|
108 /* Channel mask for setting correct channel in writeMidiEvent */ |
|
109 private static final byte MIDI_EVENT_CHANNEL_MASK = (byte)0xF0; |
|
110 |
|
111 /* Maximum value for midi variable length quantity */ |
|
112 private static final int MIDI_VARIABLE_LENGTH_MAX_VALUE = 0x0FFFFFFF; |
|
113 |
|
114 // Tone constants |
|
115 |
|
116 /* Tone resolution is expressed in pulses per full note, whereas |
|
117 midi resolution is pulses per quarter note. Thus we must divide tone |
|
118 empo by 4. For tone, 64 is considered default */ |
|
119 private static final byte TONE_DEFAULT_RESOLUTION = 64; // 64/4 = 16(ppqn) |
|
120 |
|
121 /* Default tempo for tone is 30. For bpm value it is multiplied by 4 */ |
|
122 private static final byte TONE_DEFAULT_TEMPO = 30; // 4*30 = 120 (bpm) |
|
123 |
|
124 /* Tone multiplier is used for both dividing resolution and multiplying |
|
125 tempo to get equivalent midi values */ |
|
126 private static final byte TONE_MULTIPLIER = 1; |
|
127 |
|
128 // MEMBER DATA |
|
129 |
|
130 /* Midi channel for generated MIDI sequence */ |
|
131 private byte iChannel; |
|
132 |
|
133 /* Tempo in MIDI terms */ |
|
134 private int iTempo; |
|
135 |
|
136 /* Resolution in MIDI terms */ |
|
137 private int iResolution; |
|
138 |
|
139 /* Instrument used to represent tone */ |
|
140 private byte iInstrument; |
|
141 |
|
142 /* Counter for written midi events */ |
|
143 private int iMidiEventsByteCount; |
|
144 |
|
145 /* MIDI sequence written using writeEvent( ) */ |
|
146 private ByteArrayOutputStream iMidiTrackEvents; |
|
147 |
|
148 /* Tone sequence duration */ |
|
149 private int iDuration; |
|
150 |
|
151 /** |
|
152 * Constructor |
|
153 * @param aChannel MIDI channel which is assigned to generate track |
|
154 * @param aInstrument Instrument used to represent tone |
|
155 */ |
|
156 MidiSequence(byte aChannel, byte aInstrument) |
|
157 { |
|
158 iChannel = aChannel; |
|
159 iInstrument = aInstrument; |
|
160 iTempo = TONE_DEFAULT_TEMPO * TONE_MULTIPLIER; |
|
161 iResolution = TONE_DEFAULT_RESOLUTION / TONE_MULTIPLIER; |
|
162 iMidiTrackEvents = new ByteArrayOutputStream( |
|
163 MIDI_EVENTS_TRACK_GRANULARITY); |
|
164 } |
|
165 |
|
166 /** |
|
167 * Get midi stream |
|
168 */ |
|
169 public ByteArrayInputStream getStream() throws IOException |
|
170 { |
|
171 iMidiTrackEvents.flush(); |
|
172 byte[] midiTrackEvents = iMidiTrackEvents.toByteArray(); |
|
173 ByteArrayOutputStream concateStream = |
|
174 new ByteArrayOutputStream( |
|
175 FILE_HEADER_LENGTH + |
|
176 MTRK_HEADER_LENGTH + |
|
177 TRACK_HEADER_LENGTH + |
|
178 midiTrackEvents.length + |
|
179 TRACK_TRAILER_LENGTH); |
|
180 |
|
181 writeHeader(concateStream, midiTrackEvents.length); |
|
182 concateStream.write(midiTrackEvents); |
|
183 writeTrailer(concateStream); |
|
184 |
|
185 ByteArrayInputStream midi = new ByteArrayInputStream( |
|
186 concateStream.toByteArray()); |
|
187 |
|
188 concateStream.close(); |
|
189 return midi; |
|
190 } |
|
191 |
|
192 /** |
|
193 * Get midi file data as byte[] |
|
194 */ |
|
195 public byte[] getByteArray() throws IOException |
|
196 { |
|
197 iMidiTrackEvents.flush(); |
|
198 byte[] midiTrackEvents = iMidiTrackEvents.toByteArray(); |
|
199 ByteArrayOutputStream concateStream = |
|
200 new ByteArrayOutputStream( |
|
201 FILE_HEADER_LENGTH + |
|
202 MTRK_HEADER_LENGTH + |
|
203 TRACK_HEADER_LENGTH + |
|
204 midiTrackEvents.length + |
|
205 TRACK_TRAILER_LENGTH); |
|
206 |
|
207 writeHeader(concateStream, midiTrackEvents.length); |
|
208 concateStream.write(midiTrackEvents); |
|
209 writeTrailer(concateStream); |
|
210 |
|
211 byte[] midi = concateStream.toByteArray(); |
|
212 concateStream.close(); |
|
213 return midi; |
|
214 } |
|
215 |
|
216 /** |
|
217 * Set tempo |
|
218 * @param aTempo tempo in tone sequence terms |
|
219 */ |
|
220 public void setTempo(int aTempo) |
|
221 { |
|
222 if (aTempo < MidiToneConstants.TONE_TEMPO_MIN || |
|
223 aTempo > MidiToneConstants.TONE_TEMPO_MAX) |
|
224 { |
|
225 throw new IllegalArgumentException("Tempo is out of range, " + |
|
226 "valid range is 5 <= tempo <= 127"); |
|
227 } |
|
228 iTempo = aTempo * TONE_MULTIPLIER; |
|
229 } |
|
230 |
|
231 /** |
|
232 * Set resolution |
|
233 * @param aResolution resolution in tone sequence terms |
|
234 */ |
|
235 public void setResolution(int aResolution) |
|
236 { |
|
237 if (aResolution < MidiToneConstants.TONE_RESOLUTION_MIN || |
|
238 aResolution > MidiToneConstants.TONE_RESOLUTION_MAX) |
|
239 { |
|
240 throw new IllegalArgumentException("Resolution is out of range, " + |
|
241 "valid range is 1 <= resolution <= 127"); |
|
242 } |
|
243 iResolution = aResolution / TONE_MULTIPLIER; |
|
244 } |
|
245 |
|
246 /* |
|
247 * Write midi event to stream. This method writes both variable length |
|
248 * delta time and midi event. |
|
249 * @param aLength time between last event and this event (delta time) |
|
250 * @param aCommand MIDI command byte |
|
251 * @param aEvent First MIDI command parameter |
|
252 * @param aData Second MIDI command parameter |
|
253 */ |
|
254 public void writeMidiEvent(int aLength, |
|
255 byte aCommand, |
|
256 byte aEvent, |
|
257 byte aData) |
|
258 throws MidiSequenceException |
|
259 { |
|
260 if (iMidiEventsByteCount > MIDI_EVENTS_MAX_BYTE_COUNT) |
|
261 { |
|
262 throw new MidiSequenceException(); |
|
263 } |
|
264 iMidiEventsByteCount += writeVarLen(iMidiTrackEvents, aLength); |
|
265 |
|
266 // Write down cumulative count of event lengths (sum will |
|
267 // make up duration of this midi sequence. Only audible events |
|
268 // are counted, which means only those delta times which |
|
269 // are associated to NOTE_OFF events |
|
270 if (aCommand == MidiToneConstants.MIDI_NOTE_OFF) |
|
271 { |
|
272 iDuration += aLength; |
|
273 } |
|
274 |
|
275 // attach correct channel number |
|
276 aCommand &= MIDI_EVENT_CHANNEL_MASK; |
|
277 aCommand |= iChannel; |
|
278 |
|
279 iMidiTrackEvents.write(aCommand); |
|
280 iMidiTrackEvents.write(aEvent); |
|
281 iMidiTrackEvents.write(aData); |
|
282 iMidiEventsByteCount += MIDI_EVENT_COMMAND_LENGTH; |
|
283 } |
|
284 |
|
285 /** |
|
286 * Write time interval value as MIDI variable length data to byte array. |
|
287 * @param aOut output stream |
|
288 * @param aValue time before the event in question happens, relative to |
|
289 * current time. Must be between 0 and 0x0FFFFFFF |
|
290 */ |
|
291 private int writeVarLen(ByteArrayOutputStream aOut, int aValue) |
|
292 { |
|
293 if ((aValue > MIDI_VARIABLE_LENGTH_MAX_VALUE) || (aValue < 0)) |
|
294 { |
|
295 throw new IllegalArgumentException("Input(time) value is not within range"); |
|
296 } |
|
297 |
|
298 // Variable to hold count of bytes written to output stream. |
|
299 // Value range is 1-4. |
|
300 int byteCount = 0; |
|
301 |
|
302 // variable length quantity can any hold unsigned integer value which |
|
303 // can be represented with 7-28 bits. It is written out so that 7 low |
|
304 // bytes of each byte hold part of the value and 8th byte indicates |
|
305 // whether it is last byte or not (0 if is, 1 if not). Thus a variable |
|
306 // length quantity can be 1-4 bytes long. |
|
307 |
|
308 int buffer = aValue & 0x7F; // put low 7 bytes to buffer |
|
309 |
|
310 // check if bits above 7 first are significant, 7 bits at time. If |
|
311 // they are, buffer is shifted 8 bits left and the new 7 bits are |
|
312 // appended to beginning of buffer. The eigth byte from right is |
|
313 // set 1 to indicate that that there is at least another 7 bits |
|
314 // on left (bits 9-15) which are part of the quantity. |
|
315 |
|
316 // Example. Integer 00000100 11111010 10101010 01010101 |
|
317 // 1) Set low 7 bytes to buffer => 1010101 |
|
318 // 2) Check if there is more significant bytes in the integer. If |
|
319 // is, continue. |
|
320 // 3) Shift buffer 8 left => 1010101 00000000 |
|
321 // 4) Append next 7 bytes to beginning of buffer |
|
322 // buffer => 1010101 01010100 |
|
323 // 5) Set 8th bit 1 to indicate that there is another 7 bits on left |
|
324 // buffer => 1010101 11010100 |
|
325 // 6) repeat from step 2 |
|
326 |
|
327 aValue >>= 7; |
|
328 while (aValue != 0) |
|
329 { |
|
330 buffer <<= 8; |
|
331 buffer |= ((aValue & 0x7F) | 0x80); |
|
332 aValue >>= 7; |
|
333 } |
|
334 |
|
335 // write the buffer out as 1-4 bytes. |
|
336 while (true) |
|
337 { |
|
338 aOut.write(buffer); |
|
339 byteCount++; |
|
340 |
|
341 // check if the indicator bit (8th) is set. |
|
342 // If it is, continue writing. |
|
343 int tempBuf = buffer & 0x80; |
|
344 if (tempBuf != 0) |
|
345 { |
|
346 buffer >>= 8; |
|
347 } |
|
348 else |
|
349 { |
|
350 break; |
|
351 } |
|
352 } |
|
353 return byteCount; |
|
354 } |
|
355 |
|
356 /** |
|
357 * Writes midi header |
|
358 * @param aOut output stream |
|
359 * @param aMidiEventsLength lenght of midi event content in bytes |
|
360 */ |
|
361 private void writeHeader( |
|
362 ByteArrayOutputStream aOut, |
|
363 int aMidiEventsLength) |
|
364 throws IOException |
|
365 { |
|
366 // MIDI FILE HEADER |
|
367 |
|
368 // write 'MThd' block id |
|
369 aOut.write(MIDI_HEADER_MTHD); |
|
370 |
|
371 // write MThd block length |
|
372 aOut.write(MIDI_HEADER_MTHD_LENGTH); |
|
373 |
|
374 // write midi format; format is 0 |
|
375 aOut.write(MIDI_HEADER_MIDI_FORMAT); |
|
376 |
|
377 // write MTrk chunk amount; only one track |
|
378 aOut.write(MIDI_HEADER_MTRK_CHUNK_AMOUNT); |
|
379 |
|
380 // write PPQN resolution (pulses per quarternote) |
|
381 aOut.write(MIDI_HEADER_PPQN_FIRST_BYTE); |
|
382 aOut.write(iResolution); |
|
383 |
|
384 // MTrk HEADER |
|
385 |
|
386 // write 'MTrk' for the only track |
|
387 aOut.write(MIDI_HEADER_MTRK); |
|
388 |
|
389 // calculate real track length |
|
390 int trackLength = TRACK_HEADER_LENGTH + |
|
391 aMidiEventsLength + |
|
392 TRACK_TRAILER_LENGTH; |
|
393 |
|
394 // write track length in bytes. |
|
395 // Literal numeric values (24,16,8) indicate shift offset in bits |
|
396 // 0xFF is used to mask out everything but the lowest byte. |
|
397 aOut.write((trackLength >> 24) & 0xFF); |
|
398 aOut.write((trackLength >> 16) & 0xFF); |
|
399 aOut.write((trackLength >> 8) & 0xFF); |
|
400 aOut.write(trackLength & 0xFF); |
|
401 |
|
402 // TRACK HEADER |
|
403 |
|
404 // write tempo change at beginning |
|
405 aOut.write(TRACK_HEADER_TEMPO_CHANGE); |
|
406 |
|
407 // calculate tempo in microseconds per quarter note |
|
408 int mpqn = MINUTE_AS_MICROSECONDS / iTempo; |
|
409 |
|
410 // write tempo value |
|
411 // Literal numeric values (16,8) indicate shift offset in bits |
|
412 // 0xFF is used to mask out everything but the lowest byte. |
|
413 aOut.write((mpqn >> 16) & 0xFF); |
|
414 aOut.write((mpqn >> 8) & 0xFF); |
|
415 aOut.write(mpqn & 0xFF); |
|
416 |
|
417 // change program at beginning (at delta time 0) |
|
418 writeVarLen(aOut, 0); |
|
419 aOut.write((byte)(MidiToneConstants.MIDI_PROGRAM_CHANGE | iChannel)); |
|
420 aOut.write(iInstrument); // instrument number |
|
421 } |
|
422 |
|
423 /** |
|
424 * Write midi trailer |
|
425 * @param aOut output stream |
|
426 */ |
|
427 private void writeTrailer(ByteArrayOutputStream aOut) throws IOException |
|
428 { |
|
429 aOut.write(TRACK_TRAILER); |
|
430 } |
|
431 |
|
432 /** |
|
433 * Return duration accumulated so far. |
|
434 * @return long duration in microseconds |
|
435 */ |
|
436 public long getCumulativeDuration() |
|
437 { |
|
438 // duration * seconds in minute * microseconds in second / |
|
439 // (resolution * tempo) |
|
440 long duration = (long)iDuration * 60 * 1000000 / (iResolution * iTempo); |
|
441 return duration; |
|
442 } |
|
443 |
|
444 |
|
445 } // end of class |
|
446 |