/* ============================================================================ The Apache Software License, Version 1.1 ============================================================================ Copyright (C) 1999-2002 The Apache Software Foundation. All rights reserved. Redistribution and use in source and binary forms, with or without modifica- tion, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The end-user documentation included with the redistribution, if any, must include the following acknowledgment: "This product includes software developed by the Apache Software Foundation (http://www.apache.org/)." Alternately, this acknowledgment may appear in the software itself, if and wherever such third-party acknowledgments normally appear. 4. The names "Apache Cocoon" and "Apache Software Foundation" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact apache@apache.org. 5. Products derived from this software may not be called "Apache", nor may "Apache" appear in their name, without prior written permission of the Apache Software Foundation. THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This software consists of voluntary contributions made by many individuals on behalf of the Apache Software Foundation and was originally created by Stefano Mazzocchi . For more information on the Apache Software Foundation, please see . */ package org.apache.cocoon.generation; import java.io.IOException; import java.io.InputStream; import java.util.Hashtable; import java.util.Map; import org.apache.avalon.framework.parameters.ParameterException; import org.apache.avalon.framework.parameters.Parameterizable; import org.apache.avalon.framework.parameters.Parameters; import org.apache.cocoon.ProcessingException; import org.apache.cocoon.components.source.SourceUtil; import org.apache.cocoon.environment.SourceResolver; import org.apache.cocoon.xml.XMLConsumer; import org.apache.excalibur.source.Source; import org.apache.excalibur.source.SourceException; import org.apache.excalibur.source.SourceNotFoundException; import org.apache.excalibur.source.SourceValidity; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; /** * Reads a standard MIDI file and generates SAX Events. * * The MIDI file parsing parts of this class are based on code from the XMidi project, written * by Peter Arthur Loeb (http://www.palserv.com/XMidi/) and used with permission. * The warranty disclaimer of the MIT license (http://www.opensource.org/licenses/mit-license.html) * applies to Peter Arthur Loeb's code. * * @author Mark Leicester * @author Peter Loeb */ public class MIDIGenerator extends ComposerGenerator implements Parameterizable { /** The input source */ protected Source inputSource; private boolean global_verbose; private boolean local_verbose; private static final boolean VERBOSE_DEFAULT = true; private boolean validateVerbosity(String verbose) { if (verbose.equalsIgnoreCase("TRUE")) { return true; } else if (verbose.equalsIgnoreCase("FALSE")) { return false; } return VERBOSE_DEFAULT; } public void parameterize(Parameters parameters) throws ParameterException { global_verbose = validateVerbosity( parameters.getParameter("verbose", String.valueOf(VERBOSE_DEFAULT))); } /** * Recycle this component. * All instance variables are set to null. */ public void recycle() { if (null != this.inputSource) { super.resolver.release(this.inputSource); this.inputSource = null; } // Reinitialize variables initializeVariables(); super.recycle(); } /** * Setup the MIDI file generator. * Try to get the last modification date of the source for caching. */ public void setup( SourceResolver resolver, Map objectModel, String src, Parameters par) throws ProcessingException, SAXException, IOException { super.setup(resolver, objectModel, src, par); // Initialize lookup tables initializeLookupTables(); try { this.inputSource = resolver.resolveURI(src); } catch (SourceException se) { throw SourceUtil.handle("Error resolving '" + src + "'.", se); } local_verbose = validateVerbosity( parameters.getParameter("verbose", String.valueOf(global_verbose))); } /** * Generate the unique key. * This key must be unique inside the space of this component. * * @return The generated key hashes the src */ public java.io.Serializable getKey() { return this.inputSource.getURI(); } /** * Generate the validity object. * * @return The generated validity object or null if the * component is currently not cacheable. */ public SourceValidity getValidity() { return this.inputSource.getValidity(); } /** * Generate XML data. */ public void generate() throws IOException, SAXException, ProcessingException { try { if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("processing file " + super.source); this.getLogger().debug("file resolved to " + this.inputSource.getURI()); } parseMIDI(this.inputSource, super.xmlConsumer); } catch (SAXException e) { final Exception cause = e.getException(); if (cause != null) { if (cause instanceof ProcessingException) throw (ProcessingException) cause; if (cause instanceof IOException) throw (IOException) cause; if (cause instanceof SAXException) throw (SAXException) cause; throw new ProcessingException( "Could not read resource " + this.inputSource.getURI(), cause); } throw e; } } private static final String VERSION = "1.2"; private int midiFormat; private int ppnq; private Hashtable ffHash; private String[] chanArray; private String[] fArray; private int numTracks; private int trkCount; private String parserName; private String debugFile; private String[] notes; private int[] register; private Hashtable contHash; /** * @param source * @param consumer */ private void parseMIDI(Source source, XMLConsumer consumer) throws SAXException, SourceNotFoundException, IOException, ProcessingException { InputStream inputStream = source.getInputStream(); AttributesImpl attr = new AttributesImpl(); String text = ""; this.contentHandler.startDocument(); attr.addAttribute("", "VERSION", "VERSION", "CDATA", VERSION); this.contentHandler.startElement("", "XMidi", "XMidi", attr); boolean chunkFlag = true; while (chunkFlag) { // for each chunk byte[] hdr = new byte[8]; int r; r = inputStream.read(hdr); if (r == -1) { chunkFlag = false; continue; } if (r < 8) { this.getLogger().debug("Getting header"); } // get chunk id String cid = baToString(hdr, 0, 3); // get chunk length int len = baToInt(hdr, 4, 7); // get rest of chunk byte[] dta = new byte[len]; r = inputStream.read(dta); if (r < len) { throw new ProcessingException("Getting data"); } if (cid.equals("MThd")) { attr.clear(); attr.addAttribute("", "LENGTH", "LENGTH", "CDATA", "" + len); attr.addAttribute("", "TYPE", "TYPE", "CDATA", cid); this.contentHandler.startElement("", "MThd", "MThd", attr); midiFormat = baToInt(dta, 0, 1); numTracks = baToInt(dta, 2, 3); ppnq = baToInt(dta, 4, 5); String pnq = baToHex(dta, 4, 5); attr.clear(); this.contentHandler.startElement("", "FORMAT", "FORMAT", attr); text = "" + midiFormat; this.contentHandler.characters(text.toCharArray(), 0, text.length()); this.contentHandler.endElement("", "FORMAT", "FORMAT"); this.contentHandler.startElement("", "TRACKS", "TRACKS", attr); text = "" + numTracks; this.contentHandler.characters(text.toCharArray(), 0, text.length()); this.contentHandler.endElement("", "TRACKS", "TRACKS"); this.contentHandler.startElement("", "PPNQ", "PPNQ", attr); text = pnq; this.contentHandler.characters(text.toCharArray(), 0, text.length()); this.contentHandler.endElement("", "PPNQ", "PPNQ"); this.contentHandler.endElement("", "MThd", "MThd"); } else if (cid.equals("MTrk")) { trkCount++; if (trkCount > numTracks) { throw new ProcessingException("too many tracks"); } attr.clear(); attr.addAttribute("", "LENGTH", "LENGTH", "CDATA", "" + len); attr.addAttribute("", "TYPE", "TYPE", "CDATA", cid); this.contentHandler.startElement("", "MTrk", "MTrk", attr); doTrack(dta, len); this.contentHandler.endElement("", "MTrk", "MTrk"); } else { attr.clear(); attr.addAttribute("", "LENGTH", "LENGTH", "CDATA", "" + len); attr.addAttribute("", "TYPE", "TYPE", "CDATA", cid); this.contentHandler.startElement("", "CHUNK", "CHUNK", attr); doHexData(dta, len); this.contentHandler.endElement("", "CHUNK", "CHUNK"); } } this.contentHandler.endElement("", "XMidi", "XMidi"); this.contentHandler.endDocument(); } void initializeVariables() { numTracks = 0; trkCount = 0; } void initializeLookupTables() { ffHash = new Hashtable(); ffHash.put("00", "Sequence Number"); ffHash.put("01", "Text"); ffHash.put("02", "Copyright"); ffHash.put("03", "Sequence/Track Name"); ffHash.put("04", "Instrument"); ffHash.put("05", "Lyric"); ffHash.put("06", "Marker"); ffHash.put("07", "Cue Point"); ffHash.put("20", "MIDI Channel"); ffHash.put("21", "MIDI Port"); ffHash.put("2F", "End of Track"); ffHash.put("51", "Tempo"); ffHash.put("54", "SMPTE Offset"); ffHash.put("58", "Time Signature"); ffHash.put("59", "Key Signature"); ffHash.put("7F", "Proprietary Event"); chanArray = new String[7]; chanArray[0] = "Note Off"; chanArray[1] = "Note On"; chanArray[2] = "After Touch"; chanArray[3] = "Control Change"; chanArray[4] = "Program Change"; chanArray[5] = "Channel Pressure"; chanArray[6] = "Pitch Wheel"; fArray = new String[16]; fArray[0] = "SYSEX"; fArray[1] = "MTC Quarter Frame Message"; fArray[2] = "Song Position Pointer"; fArray[3] = "Song Select"; fArray[4] = "Undefined"; fArray[5] = "Undefined"; fArray[6] = "Tune Request"; fArray[7] = "Unsupported"; fArray[8] = "MIDI Clock"; fArray[9] = "Undefined"; fArray[10] = "MIDI Start"; fArray[11] = "MIDI Continue"; fArray[12] = "MIDI Stop"; fArray[13] = "Undefined"; fArray[14] = "Active Sense"; fArray[15] = "NotUnderstood"; contHash = new Hashtable(); contHash.put("0", "Bank Select"); contHash.put("1", "Modulation Wheel (coarse)"); contHash.put("2", "Breath controller (coarse)"); contHash.put("4", "Foot Pedal (coarse)"); contHash.put("5", "Portamento Time (coarse)"); contHash.put("6", "Data Entry (coarse)"); contHash.put("7", "Volume (coarse)"); contHash.put("8", "Balance (coarse)"); contHash.put("10", "Pan position (coarse)"); contHash.put("11", "Expression (coarse)"); contHash.put("12", "Effect Control 1 (coarse)"); contHash.put("13", "Effect Control 2 (coarse)"); contHash.put("16", "General Purpose Slider 1"); contHash.put("17", "General Purpose Slider 2"); contHash.put("18", "General Purpose Slider 3"); contHash.put("19", "General Purpose Slider 4"); contHash.put("32", "Bank Select (fine)"); contHash.put("33", "Modulation Wheel (fine)"); contHash.put("34", "Breath controller (fine)"); contHash.put("36", "Foot Pedal (fine)"); contHash.put("37", "Portamento Time (fine)"); contHash.put("38", "Data Entry (fine)"); contHash.put("39", "Volume (fine)"); contHash.put("40", "Balance (fine)"); contHash.put("42", "Pan position (fine)"); contHash.put("43", "Expression (fine)"); contHash.put("44", "Effect Control 1 (fine)"); contHash.put("45", "Effect Control 2 (fine)"); contHash.put("64", "Hold Pedal (on/off)"); contHash.put("65", "Portamento (on/off)"); contHash.put("66", "Sustenuto Pedal (on/off)"); contHash.put("67", "Soft Pedal (on/off)"); contHash.put("68", "Legato Pedal (on/off)"); contHash.put("69", "Hold 2 Pedal (on/off)"); contHash.put("70", "Sound Variation"); contHash.put("71", "Sound Timbre"); contHash.put("72", "Sound Release Time"); contHash.put("73", "Sound Attack Time"); contHash.put("74", "Sound Brightness"); contHash.put("75", "Sound Control 6"); contHash.put("76", "Sound Control 7"); contHash.put("77", "Sound Control 8"); contHash.put("78", "Sound Control 9"); contHash.put("79", "Sound Control 10"); contHash.put("80", "General Purpose Button 1 (on/off)"); contHash.put("81", "General Purpose Button 2 (on/off)"); contHash.put("82", "General Purpose Button 3 (on/off)"); contHash.put("83", "General Purpose Button 4 (on/off)"); contHash.put("91", "Effects Level"); contHash.put("92", "Tremulo Level"); contHash.put("93", "Chorus Level"); contHash.put("94", "Celeste Level"); contHash.put("95", "Phaser Level"); contHash.put("96", "Data Button increment"); contHash.put("97", "Data Button decrement"); contHash.put("98", "Non-registered Parameter (fine)"); contHash.put("99", "Non-registered Parameter (coarse)"); contHash.put("100", "Registered Parameter (fine)"); contHash.put("101", "Registered Parameter (coarse)"); contHash.put("120", "All Sound Off"); contHash.put("121", "All Controllers Off"); contHash.put("122", "Local Keyboard (on/off)"); contHash.put("123", "All Notes Off"); contHash.put("124", "Omni Mode Off"); contHash.put("125", "Omni Mode On"); contHash.put("126", "Mono Operation"); contHash.put("127", "Poly Operation"); notes = new String[128]; register = new int[128]; notes[0] = "C"; register[0] = -5; notes[1] = "C#"; register[1] = -5; notes[2] = "D"; register[2] = -5; notes[3] = "Eb"; register[3] = -5; notes[4] = "E"; register[4] = -5; notes[5] = "F"; register[5] = -5; notes[6] = "F#"; register[6] = -5; notes[7] = "G"; register[7] = -5; notes[8] = "Ab"; register[8] = -5; notes[9] = "A"; register[9] = -5; notes[10] = "Bb"; register[10] = -5; notes[11] = "B"; register[11] = -5; notes[12] = "C"; register[12] = -4; notes[13] = "C#"; register[13] = -4; notes[14] = "D"; register[14] = -4; notes[15] = "Eb"; register[15] = -4; notes[16] = "E"; register[16] = -4; notes[17] = "F"; register[17] = -4; notes[18] = "F#"; register[18] = -4; notes[19] = "G"; register[19] = -4; notes[20] = "Ab"; register[20] = -4; notes[21] = "A"; register[21] = -4; notes[22] = "Bb"; register[22] = -4; notes[23] = "B"; register[23] = -4; notes[24] = "C"; register[24] = -3; notes[25] = "C#"; register[25] = -3; notes[26] = "D"; register[26] = -3; notes[27] = "Eb"; register[27] = -3; notes[28] = "E"; register[28] = -3; notes[29] = "F"; register[29] = -3; notes[30] = "F#"; register[30] = -3; notes[31] = "G"; register[31] = -3; notes[32] = "Ab"; register[32] = -3; notes[33] = "A"; register[33] = -3; notes[34] = "Bb"; register[34] = -3; notes[35] = "B"; register[35] = -3; notes[36] = "C"; register[36] = -2; notes[37] = "C#"; register[37] = -2; notes[38] = "D"; register[38] = -2; notes[39] = "Eb"; register[39] = -2; notes[40] = "E"; register[40] = -2; notes[41] = "F"; register[41] = -2; notes[42] = "F#"; register[42] = -2; notes[43] = "G"; register[43] = -2; notes[44] = "Ab"; register[44] = -2; notes[45] = "A"; register[45] = -2; notes[46] = "Bb"; register[46] = -2; notes[47] = "B"; register[47] = -2; notes[48] = "C"; register[48] = -1; notes[49] = "C#"; register[49] = -1; notes[50] = "D"; register[50] = -1; notes[51] = "Eb"; register[51] = -1; notes[52] = "E"; register[52] = -1; notes[53] = "F"; register[53] = -1; notes[54] = "F#"; register[54] = -1; notes[55] = "G"; register[55] = -1; notes[56] = "Ab"; register[56] = -1; notes[57] = "A"; register[57] = -1; notes[58] = "Bb"; register[58] = -1; notes[59] = "B"; register[59] = -1; notes[60] = "C"; register[60] = 0; notes[61] = "C#"; register[61] = 0; notes[62] = "D"; register[62] = 0; notes[63] = "Eb"; register[63] = 0; notes[64] = "E"; register[64] = 0; notes[65] = "F"; register[65] = 0; notes[66] = "F#"; register[66] = 0; notes[67] = "G"; register[67] = 0; notes[68] = "Ab"; register[68] = 0; notes[69] = "A"; register[69] = 0; notes[70] = "Bb"; register[70] = 0; notes[71] = "B"; register[71] = 0; notes[72] = "C"; register[72] = 1; notes[73] = "C#"; register[73] = 1; notes[74] = "D"; register[74] = 1; notes[75] = "Eb"; register[75] = 1; notes[76] = "E"; register[76] = 1; notes[77] = "F"; register[77] = 1; notes[78] = "F#"; register[78] = 1; notes[79] = "G"; register[79] = 1; notes[80] = "Ab"; register[80] = 1; notes[81] = "A"; register[81] = 1; notes[82] = "Bb"; register[82] = 1; notes[83] = "B"; register[83] = 1; notes[84] = "C"; register[84] = 2; notes[85] = "C#"; register[85] = 2; notes[86] = "D"; register[86] = 2; notes[87] = "Eb"; register[87] = 2; notes[88] = "E"; register[88] = 2; notes[89] = "F"; register[89] = 2; notes[90] = "F#"; register[90] = 2; notes[91] = "G"; register[91] = 2; notes[92] = "Ab"; register[92] = 2; notes[93] = "A"; register[93] = 2; notes[94] = "Bb"; register[94] = 2; notes[95] = "B"; register[95] = 2; notes[96] = "C"; register[96] = 3; notes[97] = "C#"; register[97] = 3; notes[98] = "D"; register[98] = 3; notes[99] = "Eb"; register[99] = 3; notes[100] = "E"; register[100] = 3; notes[101] = "F"; register[101] = 3; notes[102] = "F#"; register[102] = 3; notes[103] = "G"; register[103] = 3; notes[104] = "Ab"; register[104] = 3; notes[105] = "A"; register[105] = 3; notes[106] = "Bb"; register[106] = 3; notes[107] = "B"; register[107] = 3; notes[108] = "C"; register[108] = 4; notes[109] = "C#"; register[109] = 4; notes[110] = "D"; register[110] = 4; notes[111] = "Eb"; register[111] = 4; notes[112] = "E"; register[112] = 4; notes[113] = "F"; register[113] = 4; notes[114] = "F#"; register[114] = 4; notes[115] = "G"; register[115] = 4; notes[116] = "Ab"; register[116] = 4; notes[117] = "A"; register[117] = 4; notes[118] = "Bb"; register[118] = 4; notes[119] = "B"; register[119] = 4; notes[120] = "C"; register[120] = 5; notes[121] = "C#"; register[121] = 5; notes[122] = "D"; register[122] = 5; notes[123] = "Eb"; register[123] = 5; notes[124] = "E"; register[124] = 5; notes[125] = "F"; register[125] = 5; notes[126] = "F#"; register[126] = 5; notes[127] = "G"; register[127] = 5; } /** add track data to DOM structure */ void doTrack(byte[] dta, int len) throws SAXException, ProcessingException { AttributesImpl attr = new AttributesImpl(); String text = ""; boolean tFlag = true; int offset = 0; // initialize variables int dlen = 0; String edata = null; String snam = null; String nmData = ""; String ctag = null; String ctagAttr[] = new String[4]; String ctagAttrVal[] = new String[4]; int ctagnum = 0; String cnum = null; String ctyp = null; int slen = 0; int chanType = 0; int hiName = 0; int status = 0; int noff = 0; String sval = null; boolean ecFlag = true; // assume edata boolean nFlag = true; // use slen for noff boolean firstTime = true; while (tFlag) { // do delta ByteLen bl = deltaToInt(dta, offset); offset += bl.len; String deltaLen = baToHex(bl.ba, 0, 3); nFlag = true; // assume simple (slen) offset // may or may not be status boolean statFlag = false; int first = baToInt(dta, offset, offset); this.getLogger().debug( "doTrack: in loop; deltaLen=" + deltaLen + ", len=" + bl.len + ", first=" + first); if ((first & 128) == 128) { // it is a status byte statFlag = true; sval = baToHex(dta, offset, offset); status = first; this.getLogger().debug("doTrack: have status: " + sval); if (status < 240 && status > 127) { ecFlag = false; chanType = (status - 128) / 16; snam = chanArray[chanType]; ctyp = sval.substring(0, 1); cnum = sval.substring(1, 2); } else { ecFlag = true; if (status > 239 && status < 256) { hiName = status - 240; snam = fArray[hiName]; } else { throw new ProcessingException("Invalid status: " + status); } } offset++; } else { this.getLogger().debug("doTrack: running status"); } nmData = ""; if (firstTime) { firstTime = false; if (!statFlag) { throw new ProcessingException( "first time, but no status; first = " + first); } } // offset points to the byte after the status // or first byte of "running status" attr.clear(); attr.addAttribute("", "DTIME", "DTIME", "CDATA", "" + deltaLen); this.contentHandler.startElement("", "DELTA", "DELTA", attr); if (status > 127 && status < 144) { // note off slen = 2; // set up tag int pitch = baToInt(dta, offset, offset); int vel = baToInt(dta, offset + 1, offset + 1); ctag = "NOTE_OFF"; ctagAttr[0] = "PITCH"; ctagAttrVal[0] = "" + pitch; ctagAttr[1] = "VELOCITY"; ctagAttrVal[1] = "" + vel; ctagnum = 2; if (local_verbose) { ctagAttr[2] = "NAME"; ctagAttrVal[2] = notes[pitch]; ctagAttr[3] = "REGISTER"; ctagAttrVal[3] = "" + register[pitch]; ctagnum = 4; } } else if (status > 143 && status < 160) { // note on slen = 2; int pitch = baToInt(dta, offset, offset); int vel = baToInt(dta, offset + 1, offset + 1); ctag = "NOTE_ON"; ctagAttr[0] = "PITCH"; ctagAttrVal[0] = "" + pitch; ctagAttr[1] = "VELOCITY"; ctagAttrVal[1] = "" + vel; ctagnum = 2; if (local_verbose) { ctagAttr[2] = "NAME"; ctagAttrVal[2] = notes[pitch]; ctagAttr[3] = "REGISTER"; ctagAttrVal[3] = "" + register[pitch]; ctagnum = 4; } } else if (status > 159 && status < 176) { // after touch slen = 2; int pitch = baToInt(dta, offset, offset); int pres = baToInt(dta, offset + 1, offset + 1); ctag = "AFTER"; ctagAttr[0] = "PITCH"; ctagAttrVal[0] = "" + pitch; ctagAttr[1] = "PRESSURE"; ctagAttrVal[1] = "" + pres; ctagnum = 2; if (local_verbose) { ctagAttr[2] = "NAME"; ctagAttrVal[2] = notes[pitch]; ctagAttr[3] = "REGISTER"; ctagAttrVal[3] = "" + register[pitch]; ctagnum = 4; } } else if (status > 175 && status < 192) { // control change slen = 2; int contnum = baToInt(dta, offset, offset); int contval = baToInt(dta, offset + 1, offset + 1); ctag = "CONTROL"; ctagAttr[0] = "NUMBER"; ctagAttrVal[0] = "" + contnum; ctagAttr[1] = "VALUE"; ctagAttrVal[1] = "" + contval; ctagnum = 2; if (local_verbose) { ctagAttr[2] = "NAME"; ctagAttrVal[2] = (String) contHash.get("" + contnum); ctagnum = 3; } } else if (status > 191 && status < 208) { // program (patch) change slen = 1; int patch = baToInt(dta, offset, offset); ctag = "PROGRAM"; ctagAttr[0] = "NUMBER"; ctagAttrVal[0] = "" + patch; ctagnum = 1; } else if (status > 207 && status < 224) { // channel pressure slen = 1; int pamt = baToInt(dta, offset, offset); ctag = "PRESSURE"; ctagAttr[0] = "AMOUNT"; ctagAttrVal[0] = "" + pamt; ctagnum = 1; } else if (status > 223 && status < 240) { // pitch wheel slen = 2; int pwamt = getPW(dta, offset); ctag = "WHEEL"; ctagAttr[0] = "AMOUNT"; ctagAttrVal[0] = "" + pwamt; ctagnum = 1; } else if (status == 240) { // sysex bl = deltaToInt(dta, offset); slen = baToInt(bl.ba, 0, 3); noff = bl.len; nFlag = false; edata = baToHex(dta, offset + noff, offset + noff + slen - 1); noff += slen; } else if (status == 255) { // non midi (reset only in "live" midi") nmData = baToHex(dta, offset, offset); // nmData = "SNMT=\""+nmDta+"\" "; snam = "non-MIDI"; String nmNam = (String) ffHash.get(nmData); if (nmNam != null) { snam += " - " + nmNam; } else { snam += " - Unknown"; } // int nmt = baToInt(dta,offset+1,offset+1); bl = deltaToInt(dta, offset + 1); slen = baToInt(bl.ba, 0, 3); noff = bl.len + 1; nFlag = false; if (slen == 0) { edata = "No data"; } else { edata = baToHex( dta, offset + noff, offset + noff + slen - 1); noff += slen; } this.getLogger().debug( "doTrack: status FF" + nmData + ", edata = " + edata); } else if (status == 241 || status == 243) { int tcv = (int) dta[offset]; Integer tc = new Integer(tcv); edata = tc.toString(); slen = 1; } else if (status == 242) { int tcv = getPW(dta, offset); Integer tc = new Integer(tcv); edata = tc.toString(); slen = 2; } else if (status == 246 || status == 248 || status == 250 || status == 251 || status == 252 || status == 254) { edata = "No data for " + snam; slen = 0; } else { // really unknown int ol = getNextHiOrd(dta, offset); edata = baToHex(dta, offset + 1, ol); ol -= offset + 1; slen = ol; } if (ecFlag) { if (statFlag) { attr.clear(); attr.addAttribute("", "SLEN", "SLEN", "CDATA", "" + slen); attr.addAttribute("", "SNAM", "SNAM", "CDATA", snam); if (!nmData.equals("")) { attr.addAttribute("", "SNMT", "SNMT", "CDATA", nmData); } attr.addAttribute("", "SVAL", "SVAL", "CDATA", sval); this.contentHandler.startElement("", "STATUS", "STATUS", attr); attr.clear(); this.contentHandler.startElement("", "EDATA", "EDATA", attr); text = edata; this.contentHandler.characters(text.toCharArray(), 0, text.length()); this.contentHandler.endElement("", "EDATA", "EDATA"); this.contentHandler.endElement("", "STATUS", "STATUS"); } else { attr.clear(); this.contentHandler.startElement("", "EDATA", "EDATA", attr); text = edata; this.contentHandler.characters(text.toCharArray(), 0, text.length()); this.contentHandler.endElement("", "EDATA", "EDATA"); } } else { if (statFlag) { attr.clear(); attr.addAttribute("", "SLEN", "SLEN", "CDATA", "" + slen); attr.addAttribute("", "SNAM", "SNAM", "CDATA", snam); if (!nmData.equals("")) { attr.addAttribute("", "SNMT", "SNMT", "CDATA", nmData); } attr.addAttribute("", "SVAL", "SVAL", "CDATA", sval); this.contentHandler.startElement("", "STATUS", "STATUS", attr); attr.clear(); attr.addAttribute("", "NUMBER", "NUMBER", "CDATA", cnum); attr.addAttribute("", "TYPE", "TYPE", "CDATA", ctyp); this.contentHandler.startElement("", "CHANNEL", "CHANNEL", attr); attr.clear(); for (int c = 0; c < ctagnum; c++) { attr.addAttribute( "", ctagAttr[c], ctagAttr[c], "CDATA", ctagAttrVal[c]); } this.contentHandler.startElement("", ctag, ctag, attr); this.contentHandler.endElement("", ctag, ctag); this.contentHandler.endElement("", "CHANNEL", "CHANNEL"); this.contentHandler.endElement("", "STATUS", "STATUS"); } else { attr.clear(); attr.addAttribute("", "NUMBER", "NUMBER", "CDATA", cnum); attr.addAttribute("", "TYPE", "TYPE", "CDATA", ctyp); this.contentHandler.startElement("", "CHANNEL", "CHANNEL", attr); attr.clear(); for (int c = 0; c < ctagnum; c++) { attr.addAttribute( "", ctagAttr[c], ctagAttr[c], "CDATA", ctagAttrVal[c]); } this.contentHandler.startElement("", ctag, ctag, attr); this.contentHandler.endElement("", ctag, ctag); this.contentHandler.endElement("", "CHANNEL", "CHANNEL"); } } this.contentHandler.endElement("", "DELTA", "DELTA"); if (nFlag) { offset += slen; } else { offset += noff; } if (offset >= len) { tFlag = false; } } } int getPW(byte[] dta, int offset) throws ProcessingException { int hi = baToInt(dta, offset, offset); int lo = baToInt(dta, offset + 1, offset + 1); hi <<= 7; lo |= hi; return lo; } int getNextHiOrd(byte[] dta, int offset) throws ProcessingException { int ol = 0; boolean tflag = true; for (int o = offset + 1; o < dta.length - 1; o++) { if (tflag) { int x = baToInt(dta, o, o); if ((x & 128) == 128) { tflag = false; ol = o; } } } if (tflag) { ol = offset + dta.length; } return ol; } /** write formatted hex data to file */ void doHexData(byte[] dta, int len) throws ProcessingException, SAXException { AttributesImpl attr = new AttributesImpl(); String text = ""; this.contentHandler.startElement("", "HEXDATA", "HEXDATA", attr); StringBuffer bth = new StringBuffer(""); int bpl = 32; int r = len % bpl; int n = len / bpl; for (int i = 0; i < n; i++) { int strt = i * bpl; bth.append(baToHex(dta, strt, strt + bpl - 1)); } if (r > 0) { int strt = n * bpl; bth.append(baToHex(dta, strt, strt + r - 1)); } text = bth.toString(); this.contentHandler.characters(text.toCharArray(), 0, text.length()); this.contentHandler.endElement("", "HEXDATA", "HEXDATA"); } /** convert int to byte array of length c */ byte[] intToBa(int n, int c) throws ProcessingException { byte[] b = new byte[c]; int t = n; for (int i = 0; i < c; i++) { int j = c - i - 1; int k = t % 256; b[j] = (byte) k; t /= 256; } if (t > 0) { throw new ProcessingException( "intToBa: t is " + t + ", n = " + n + ", c = " + c); } return b; } /** convert byte array to int */ int baToInt(byte[] b, int strt, int end) throws ProcessingException { if (end > b.length - 1) { throw new ProcessingException( "baToInt: strt = " + strt + ", end = " + end + ", b.length = " + b.length); } int l = end - strt + 1; int i = 0; for (int j = 0; j < l; j++) { int p = strt + l - j - 1; // get int value of unsigned byte into k if (p > b.length - 1) { throw new ProcessingException( "baToInt: p = " + p + ", strt = " + strt + ", end = " + end + ", l = " + l + ", j = " + j + ", i = " + i); } int k = getUnsignedByte(b[p]); int n = pow(256, j); i += n * k; } return i; } /** convert byte array to hex string */ String baToHex(byte[] b, int strt, int end) throws ProcessingException { int l = end - strt + 1; if (b.length < end) { throw new ProcessingException( "baToHex: length error; b.length=" + b.length + ", strt=" + strt + ", end=" + end + ", l=" + l); } StringBuffer sb = new StringBuffer(""); for (int i = 0; i < l; i++) { int t = getUnsignedByte(b[strt + i]); int a = t / 16; int aa = t % 16; sb.append("0123456789ABCDEF".substring(a, a + 1)); sb.append("0123456789ABCDEF".substring(aa, aa + 1)); } return sb.toString(); } /** convert byte array to string (not hex) */ String baToString(byte[] b, int strt, int end) { int l = end - strt + 1; char[] c = new char[l]; int i = 0; for (int j = 0; j < l; j++) { c[j] = (char) b[j]; } return new String(c); } /** convert delta time to byte array a delta time is expressed as a byte array, length unknown (4 or less) */ public ByteLen deltaToInt(byte[] b, int offset) throws ProcessingException { /* - capture up to four bytes including first with hi-ord bit off - turn off hi-ord bits - accumulate 4 groups of 7 into 28 bit number with hi-ord 4 bits zero. */ int j = 0; byte[] ba = new byte[4]; boolean jFlag = true; while (jFlag) { if ((j + offset) > b.length) { throw new ProcessingException( "deltaToInt: length error; j = " + j + ", offset = " + offset + ", b.length = " + b.length); } ba[j] = b[j + offset]; if (ba[j] >= 0) { jFlag = false; } else { ba[j] &= 127; } this.getLogger().debug( "deltaToInt: j = " + j + ", ba = " + baToInt(ba, 0, j)); j++; } int s = 0; for (int i = 0; i < j; i++) { int k = j - i - 1; int p = pow(128, i); int m = (int) ba[k]; s += m * p; this.getLogger().debug( "deltaToInt: in loop: s = " + s + ", i = " + i + ", j = " + j + ", k = " + k + ", p = " + p + ", m = " + m); } this.getLogger().debug("deltaToInt: s = " + s); ByteLen bl = new ByteLen(intToBa(s, 4), j); return bl; } /** convert byte (unsigned) to int */ int getUnsignedByte(byte b) { int t = 0; if ((b & 128) == 128) { t = 1; } b &= 127; int k = b; k += t * 128; return k; } /** compute b to the e power (b ** e) */ int pow(int b, int e) { int a = 1; for (int i = 0; i < e; i++) { a *= b; } return a; } private class ByteLen { public ByteLen() { } /** * @param byte[] b - a byte array; used to set ba * @param int l - a length; used to set len */ public ByteLen(byte[] b, int l) { ba = b; len = l; } /** * A byte array. */ public byte[] ba; /** * It is the length of the delta field being converted, * not the length of the array. *

* There is nothing about this class that requires that this variable * be used in this way. It could be any int. */ public int len; } }