javauis/mmapi_qt/baseline/javasrc/com/nokia/microedition/media/tone/MidiSequence.java
changeset 23 98ccebc37403
equal deleted inserted replaced
21:2a9601315dfc 23:98ccebc37403
       
     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