diff -r 2a9601315dfc -r 98ccebc37403 javauis/mmapi_qt/baseline/javasrc/com/nokia/microedition/media/tone/MidiSequence.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javauis/mmapi_qt/baseline/javasrc/com/nokia/microedition/media/tone/MidiSequence.java Fri May 14 15:47:24 2010 +0300 @@ -0,0 +1,446 @@ +/* +* Copyright (c) 2002 Nokia Corporation and/or its subsidiary(-ies). +* All rights reserved. +* This component and the accompanying materials are made available +* under the terms of "Eclipse Public License v1.0" +* which accompanies this distribution, and is available +* at the URL "http://www.eclipse.org/legal/epl-v10.html". +* +* Initial Contributors: +* Nokia Corporation - initial contribution. +* +* Contributors: +* +* Description: This class represents midi sequence +* +*/ + + +package com.nokia.microedition.media.tone; + +import javax.microedition.media.control.ToneControl; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +/** + * This class represents midi sequence + */ +public class MidiSequence +{ + // CONSTANTS + + /* Value of minute expressed as microseconds */ + private static final int MINUTE_AS_MICROSECONDS = 60000000; + + /* MIDI events track stream granularity */ + private static final int MIDI_EVENTS_TRACK_GRANULARITY = 100; + + // MIDI constants + + /* Note value used for silence events. This will not be audible */ + public static final byte MIDI_SILENCE_NOTE = 0; + + // MIDI file constants + + /* Maximum length of midi sequence events. After this has been + met, no new events are accepted by writeMidiEvent. This is not + a hard limit anyway (may be exceeded by the last written event), + so it's only here to guard memory use and possible infinite + sequence */ + + private static final int MIDI_EVENTS_MAX_BYTE_COUNT = 32768; + + /* Length of MIDI file header. This includes following: + MThd block id, MThd length, midi format, MTrk chunk amount and PPQN */ + private static final byte FILE_HEADER_LENGTH = 14; + + /* Length of MTrk block. This includes: MTrk block id, MTrk length */ + private static final byte MTRK_HEADER_LENGTH = 8; + + /* Length of MIDI track header. This includes: + tempo change, tempo value, program change */ + private static final int TRACK_HEADER_LENGTH = 10; + + /* Length of MIDI track trailer */ + private static final int TRACK_TRAILER_LENGTH = 4; + + // MIDI file header constants + + /* Header block for MThd */ + private static final byte[] MIDI_HEADER_MTHD = + { 0x4D, 0x54, 0x68, 0x64 }; + + /* Header block for MThd block length; value is 6 */ + private static final byte[] MIDI_HEADER_MTHD_LENGTH = + { 0x00, 0x00, 0x00, 0x06 }; + + /* Header block for used MIDI format; format is 0 */ + private static final byte[] MIDI_HEADER_MIDI_FORMAT = + { 0x00, 0x00 }; + + /* Header block for amount of MTrk blocks used */ + private static final byte[] MIDI_HEADER_MTRK_CHUNK_AMOUNT = + { 0x00, 0x01 }; + + /* Value for first byte in PPQN block in midi file header */ + private static final byte MIDI_HEADER_PPQN_FIRST_BYTE = 0x00; + + // MIDI track constants + + /* Header block for MTrk */ + private static final byte[] MIDI_HEADER_MTRK = + { 0x4D, 0x54, 0x72, 0x6B }; + + /* Tempo change command. Includes delta time ( 0x00 ) + and command (0xFF5103) */ + private static final byte[] TRACK_HEADER_TEMPO_CHANGE = + { 0x00, (byte)0xFF, 0x51, 0x03 }; + + /* Track end meta event */ + private static final byte[] TRACK_TRAILER = + { 0x00, (byte)0xFF, 0x2F, 0x00 }; + + /* Length of single midi event without the variable length + delta time */ + private static final int MIDI_EVENT_COMMAND_LENGTH = 3; + + /* Channel mask for setting correct channel in writeMidiEvent */ + private static final byte MIDI_EVENT_CHANNEL_MASK = (byte)0xF0; + + /* Maximum value for midi variable length quantity */ + private static final int MIDI_VARIABLE_LENGTH_MAX_VALUE = 0x0FFFFFFF; + + // Tone constants + + /* Tone resolution is expressed in pulses per full note, whereas + midi resolution is pulses per quarter note. Thus we must divide tone + empo by 4. For tone, 64 is considered default */ + private static final byte TONE_DEFAULT_RESOLUTION = 64; // 64/4 = 16(ppqn) + + /* Default tempo for tone is 30. For bpm value it is multiplied by 4 */ + private static final byte TONE_DEFAULT_TEMPO = 30; // 4*30 = 120 (bpm) + + /* Tone multiplier is used for both dividing resolution and multiplying + tempo to get equivalent midi values */ + private static final byte TONE_MULTIPLIER = 1; + + // MEMBER DATA + + /* Midi channel for generated MIDI sequence */ + private byte iChannel; + + /* Tempo in MIDI terms */ + private int iTempo; + + /* Resolution in MIDI terms */ + private int iResolution; + + /* Instrument used to represent tone */ + private byte iInstrument; + + /* Counter for written midi events */ + private int iMidiEventsByteCount; + + /* MIDI sequence written using writeEvent( ) */ + private ByteArrayOutputStream iMidiTrackEvents; + + /* Tone sequence duration */ + private int iDuration; + + /** + * Constructor + * @param aChannel MIDI channel which is assigned to generate track + * @param aInstrument Instrument used to represent tone + */ + MidiSequence(byte aChannel, byte aInstrument) + { + iChannel = aChannel; + iInstrument = aInstrument; + iTempo = TONE_DEFAULT_TEMPO * TONE_MULTIPLIER; + iResolution = TONE_DEFAULT_RESOLUTION / TONE_MULTIPLIER; + iMidiTrackEvents = new ByteArrayOutputStream( + MIDI_EVENTS_TRACK_GRANULARITY); + } + + /** + * Get midi stream + */ + public ByteArrayInputStream getStream() throws IOException + { + iMidiTrackEvents.flush(); + byte[] midiTrackEvents = iMidiTrackEvents.toByteArray(); + ByteArrayOutputStream concateStream = + new ByteArrayOutputStream( + FILE_HEADER_LENGTH + + MTRK_HEADER_LENGTH + + TRACK_HEADER_LENGTH + + midiTrackEvents.length + + TRACK_TRAILER_LENGTH); + + writeHeader(concateStream, midiTrackEvents.length); + concateStream.write(midiTrackEvents); + writeTrailer(concateStream); + + ByteArrayInputStream midi = new ByteArrayInputStream( + concateStream.toByteArray()); + + concateStream.close(); + return midi; + } + + /** + * Get midi file data as byte[] + */ + public byte[] getByteArray() throws IOException + { + iMidiTrackEvents.flush(); + byte[] midiTrackEvents = iMidiTrackEvents.toByteArray(); + ByteArrayOutputStream concateStream = + new ByteArrayOutputStream( + FILE_HEADER_LENGTH + + MTRK_HEADER_LENGTH + + TRACK_HEADER_LENGTH + + midiTrackEvents.length + + TRACK_TRAILER_LENGTH); + + writeHeader(concateStream, midiTrackEvents.length); + concateStream.write(midiTrackEvents); + writeTrailer(concateStream); + + byte[] midi = concateStream.toByteArray(); + concateStream.close(); + return midi; + } + + /** + * Set tempo + * @param aTempo tempo in tone sequence terms + */ + public void setTempo(int aTempo) + { + if (aTempo < MidiToneConstants.TONE_TEMPO_MIN || + aTempo > MidiToneConstants.TONE_TEMPO_MAX) + { + throw new IllegalArgumentException("Tempo is out of range, " + + "valid range is 5 <= tempo <= 127"); + } + iTempo = aTempo * TONE_MULTIPLIER; + } + + /** + * Set resolution + * @param aResolution resolution in tone sequence terms + */ + public void setResolution(int aResolution) + { + if (aResolution < MidiToneConstants.TONE_RESOLUTION_MIN || + aResolution > MidiToneConstants.TONE_RESOLUTION_MAX) + { + throw new IllegalArgumentException("Resolution is out of range, " + + "valid range is 1 <= resolution <= 127"); + } + iResolution = aResolution / TONE_MULTIPLIER; + } + + /* + * Write midi event to stream. This method writes both variable length + * delta time and midi event. + * @param aLength time between last event and this event (delta time) + * @param aCommand MIDI command byte + * @param aEvent First MIDI command parameter + * @param aData Second MIDI command parameter + */ + public void writeMidiEvent(int aLength, + byte aCommand, + byte aEvent, + byte aData) + throws MidiSequenceException + { + if (iMidiEventsByteCount > MIDI_EVENTS_MAX_BYTE_COUNT) + { + throw new MidiSequenceException(); + } + iMidiEventsByteCount += writeVarLen(iMidiTrackEvents, aLength); + + // Write down cumulative count of event lengths (sum will + // make up duration of this midi sequence. Only audible events + // are counted, which means only those delta times which + // are associated to NOTE_OFF events + if (aCommand == MidiToneConstants.MIDI_NOTE_OFF) + { + iDuration += aLength; + } + + // attach correct channel number + aCommand &= MIDI_EVENT_CHANNEL_MASK; + aCommand |= iChannel; + + iMidiTrackEvents.write(aCommand); + iMidiTrackEvents.write(aEvent); + iMidiTrackEvents.write(aData); + iMidiEventsByteCount += MIDI_EVENT_COMMAND_LENGTH; + } + + /** + * Write time interval value as MIDI variable length data to byte array. + * @param aOut output stream + * @param aValue time before the event in question happens, relative to + * current time. Must be between 0 and 0x0FFFFFFF + */ + private int writeVarLen(ByteArrayOutputStream aOut, int aValue) + { + if ((aValue > MIDI_VARIABLE_LENGTH_MAX_VALUE) || (aValue < 0)) + { + throw new IllegalArgumentException("Input(time) value is not within range"); + } + + // Variable to hold count of bytes written to output stream. + // Value range is 1-4. + int byteCount = 0; + + // variable length quantity can any hold unsigned integer value which + // can be represented with 7-28 bits. It is written out so that 7 low + // bytes of each byte hold part of the value and 8th byte indicates + // whether it is last byte or not (0 if is, 1 if not). Thus a variable + // length quantity can be 1-4 bytes long. + + int buffer = aValue & 0x7F; // put low 7 bytes to buffer + + // check if bits above 7 first are significant, 7 bits at time. If + // they are, buffer is shifted 8 bits left and the new 7 bits are + // appended to beginning of buffer. The eigth byte from right is + // set 1 to indicate that that there is at least another 7 bits + // on left (bits 9-15) which are part of the quantity. + + // Example. Integer 00000100 11111010 10101010 01010101 + // 1) Set low 7 bytes to buffer => 1010101 + // 2) Check if there is more significant bytes in the integer. If + // is, continue. + // 3) Shift buffer 8 left => 1010101 00000000 + // 4) Append next 7 bytes to beginning of buffer + // buffer => 1010101 01010100 + // 5) Set 8th bit 1 to indicate that there is another 7 bits on left + // buffer => 1010101 11010100 + // 6) repeat from step 2 + + aValue >>= 7; + while (aValue != 0) + { + buffer <<= 8; + buffer |= ((aValue & 0x7F) | 0x80); + aValue >>= 7; + } + + // write the buffer out as 1-4 bytes. + while (true) + { + aOut.write(buffer); + byteCount++; + + // check if the indicator bit (8th) is set. + // If it is, continue writing. + int tempBuf = buffer & 0x80; + if (tempBuf != 0) + { + buffer >>= 8; + } + else + { + break; + } + } + return byteCount; + } + + /** + * Writes midi header + * @param aOut output stream + * @param aMidiEventsLength lenght of midi event content in bytes + */ + private void writeHeader( + ByteArrayOutputStream aOut, + int aMidiEventsLength) + throws IOException + { + // MIDI FILE HEADER + + // write 'MThd' block id + aOut.write(MIDI_HEADER_MTHD); + + // write MThd block length + aOut.write(MIDI_HEADER_MTHD_LENGTH); + + // write midi format; format is 0 + aOut.write(MIDI_HEADER_MIDI_FORMAT); + + // write MTrk chunk amount; only one track + aOut.write(MIDI_HEADER_MTRK_CHUNK_AMOUNT); + + // write PPQN resolution (pulses per quarternote) + aOut.write(MIDI_HEADER_PPQN_FIRST_BYTE); + aOut.write(iResolution); + + // MTrk HEADER + + // write 'MTrk' for the only track + aOut.write(MIDI_HEADER_MTRK); + + // calculate real track length + int trackLength = TRACK_HEADER_LENGTH + + aMidiEventsLength + + TRACK_TRAILER_LENGTH; + + // write track length in bytes. + // Literal numeric values (24,16,8) indicate shift offset in bits + // 0xFF is used to mask out everything but the lowest byte. + aOut.write((trackLength >> 24) & 0xFF); + aOut.write((trackLength >> 16) & 0xFF); + aOut.write((trackLength >> 8) & 0xFF); + aOut.write(trackLength & 0xFF); + + // TRACK HEADER + + // write tempo change at beginning + aOut.write(TRACK_HEADER_TEMPO_CHANGE); + + // calculate tempo in microseconds per quarter note + int mpqn = MINUTE_AS_MICROSECONDS / iTempo; + + // write tempo value + // Literal numeric values (16,8) indicate shift offset in bits + // 0xFF is used to mask out everything but the lowest byte. + aOut.write((mpqn >> 16) & 0xFF); + aOut.write((mpqn >> 8) & 0xFF); + aOut.write(mpqn & 0xFF); + + // change program at beginning (at delta time 0) + writeVarLen(aOut, 0); + aOut.write((byte)(MidiToneConstants.MIDI_PROGRAM_CHANGE | iChannel)); + aOut.write(iInstrument); // instrument number + } + + /** + * Write midi trailer + * @param aOut output stream + */ + private void writeTrailer(ByteArrayOutputStream aOut) throws IOException + { + aOut.write(TRACK_TRAILER); + } + + /** + * Return duration accumulated so far. + * @return long duration in microseconds + */ + public long getCumulativeDuration() + { + // duration * seconds in minute * microseconds in second / + // (resolution * tempo) + long duration = (long)iDuration * 60 * 1000000 / (iResolution * iTempo); + return duration; + } + + +} // end of class +