blob: 3671cae3aae3d883d377742f348f203b5dc4d472 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cocoon.serialization;
import java.io.IOException;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.components.midi.xmidi.Constants;
import org.apache.cocoon.components.midi.xmidi.Utils;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
/**
* Takes SAX Events and serializes them as a standard MIDI file.
*
* The MIDI file generation 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 <a href="mailto:mark.leicester@energyintellect.com">Mark Leicester</a>
* @author <a href="mailto:peter@palserv.com">Peter Loeb</a>
*
* @version CVS $Id$
*/
public class XMidiSerializer extends AbstractSerializer {
private final static String mimeType = "audio/x-midi";
private final static int OUTSIDE_XMIDI = 0;
private final static int INSIDE_XMIDI = 1;
private final static int INSIDE_CHUNK = 2;
private final static int INSIDE_MTHD = 3;
private final static int INSIDE_MTRK = 4;
private final static int INSIDE_DELTA = 5;
private final static int INSIDE_STATUS = 6;
private final static int INSIDE_DELTA_CHANNEL = 7;
private final static int INSIDE_STATUS_CHANNEL = 8;
private int expectedBytes;
private boolean preventDataWrite;
private int state;
private StringBuffer buffer;
private boolean buffering = false;
/* (non-Javadoc)
* @see org.apache.avalon.excalibur.pool.Recyclable#recycle()
*/
public void recycle() {
preventDataWrite = false;
state = OUTSIDE_XMIDI;
super.recycle();
}
/* (non-Javadoc)
* @see org.apache.cocoon.sitemap.SitemapOutputComponent#getMimeType()
*/
public String getMimeType() {
return (mimeType);
}
/* (non-Javadoc)
* @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
*/
public void startElement(
String namespaceURI,
String localName,
String qName,
Attributes atts)
throws SAXException {
try {
if (localName.equals("XMidi")) {
if (state != OUTSIDE_XMIDI) {
throw new SAXException("XMidi element not expected here");
}
state = INSIDE_XMIDI;
String version = atts.getValue("VERSION");
if (version == null) {
throw new SAXException("XMidi element has no version attribute");
} else if (!version.equals(Constants.VERSION)) {
throw new SAXException(
"XMidi element has wrong version: expecting "
+ Constants.VERSION
+ ", got "
+ version);
}
this.getLogger().debug(
"Found XMidi element, version " + version);
} else if (localName.equals("CHUNK")) {
if (state != INSIDE_XMIDI) {
throw new SAXException(
localName
+ " element not expected here, state = "
+ state);
}
state = INSIDE_CHUNK;
writeString(atts.getValue("TYPE"), 4);
Integer iLen = new Integer(atts.getValue("LENGTH"));
writeFullWord(iLen.intValue());
this.getLogger().debug(
"chunk type is: "
+ atts.getValue("TYPE")
+ " with length "
+ atts.getValue("LENGTH"));
} else if (localName.equals("MThd")) {
if (state != INSIDE_XMIDI) {
throw new SAXException(
localName
+ " element not expected here, state = "
+ state);
}
state = INSIDE_MTHD;
writeString(atts.getValue("TYPE"), 4);
writeFullWord(Utils.stringToInt(atts.getValue("LENGTH")));
this.getLogger().debug(
"we have MThd chunk; len = " + atts.getValue("LENGTH"));
} else if (localName.equals("MTrk")) {
if (state != INSIDE_XMIDI) {
throw new SAXException(
localName
+ " element not expected here, state = "
+ state);
}
state = INSIDE_MTRK;
writeString(atts.getValue("TYPE"), 4);
writeFullWord(Utils.stringToInt(atts.getValue("LENGTH")));
this.getLogger().debug(
"we have MTrk chunk; len = " + atts.getValue("LENGTH"));
} else if (localName.equals("DELTA")) {
if (state != INSIDE_MTRK) {
throw new SAXException(
localName
+ " element not expected here, state = "
+ state);
}
state = INSIDE_DELTA;
String dtime = atts.getValue("DTIME");
byte[] hdt = Utils.hexToBa(dtime, 4);
byte[] dt = Utils.intToDelta(hdt);
this.getLogger().debug(
"Delta: "
+ dtime
+ ", out = "
+ Utils.baToHex(dt, 0, dt.length - 1));
this.output.write(dt);
} else if (localName.equals("STATUS")) {
if (state != INSIDE_DELTA) {
throw new SAXException(
localName
+ " element not expected here, state = "
+ state);
}
state = INSIDE_STATUS;
String sval = atts.getValue("SVAL");
writeHex(sval, 1);
String sl = atts.getValue("SLEN");
expectedBytes = Utils.stringToInt(sl);
this.getLogger().debug(
"Status: " + sval + ", len = " + expectedBytes);
if (sval.equals("FF")) {
String nmd = atts.getValue("SNMT");
writeHex(nmd, 1);
byte[] hdt = Utils.intToBa(expectedBytes, 4);
byte[] xdt = Utils.intToDelta(hdt);
this.output.write(xdt);
if (expectedBytes == 0) {
preventDataWrite = true;
}
this.getLogger().debug("Non-midi: " + nmd);
} else if (sval.equals("F0")) {
byte[] hdt = Utils.intToBa(Utils.stringToInt(sl), 4);
this.output.write(Utils.intToDelta(hdt));
this.getLogger().debug("Sysex");
} else if (
sval.equals("F6")
| sval.equals("F8")
| sval.equals("FA")
| sval.equals("FB")
| sval.equals("FC")
| sval.equals("FE")) {
preventDataWrite = true;
this.getLogger().debug("no data");
}
} else if (localName.equals("CHANNEL")) {
if ((state != INSIDE_DELTA) && (state != INSIDE_STATUS)) {
throw new SAXException(
localName
+ " element not expected here, state = "
+ state);
}
if (state == INSIDE_DELTA) {
state = INSIDE_DELTA_CHANNEL;
} else if (state == INSIDE_STATUS) {
state = INSIDE_STATUS_CHANNEL;
}
this.getLogger().debug("Channel");
} else if (localName.equals("NOTE_OFF")) {
if ((state != INSIDE_DELTA_CHANNEL)
&& (state != INSIDE_STATUS_CHANNEL)) {
throw new SAXException(
localName
+ " element not expected here, state = "
+ state);
}
String pitch = atts.getValue("PITCH");
this.output.write(Utils.stringToInt(pitch));
String vel = atts.getValue("VELOCITY");
this.output.write(Utils.stringToInt(vel));
this.getLogger().debug("Note off - " + pitch + ", " + vel);
} else if (localName.equals("NOTE_ON")) {
if ((state != INSIDE_DELTA_CHANNEL)
&& (state != INSIDE_STATUS_CHANNEL)) {
throw new SAXException(
localName
+ " element not expected here, state = "
+ state);
}
String pitch = atts.getValue("PITCH");
this.output.write(Utils.stringToInt(pitch));
String vel = atts.getValue("VELOCITY");
this.output.write(Utils.stringToInt(vel));
this.getLogger().debug("Note on - " + pitch + ", " + vel);
} else if (localName.equals("AFTER")) {
if ((state != INSIDE_DELTA_CHANNEL)
&& (state != INSIDE_STATUS_CHANNEL)) {
throw new SAXException(
localName
+ " element not expected here, state = "
+ state);
}
String pitch = atts.getValue("PITCH");
this.output.write(Utils.stringToInt(pitch));
String pres = atts.getValue("PRESSURE");
this.output.write(Utils.stringToInt(pres));
this.getLogger().debug("AFTER - " + pitch + ", " + pres);
} else if (localName.equals("CONTROL")) {
if ((state != INSIDE_DELTA_CHANNEL)
&& (state != INSIDE_STATUS_CHANNEL)) {
throw new SAXException(
localName
+ " element not expected here, state = "
+ state);
}
String cnum = atts.getValue("NUMBER");
this.output.write(Utils.stringToInt(cnum));
String val = atts.getValue("VALUE");
this.output.write(Utils.stringToInt(val));
this.getLogger().debug("CONTROL - " + cnum + ", " + val);
} else if (localName.equals("PROGRAM")) {
if ((state != INSIDE_DELTA_CHANNEL)
&& (state != INSIDE_STATUS_CHANNEL)) {
throw new SAXException(
localName
+ " element not expected here, state = "
+ state);
}
String patch = atts.getValue("NUMBER");
this.output.write(Utils.stringToInt(patch));
this.getLogger().debug("PATCH - " + patch);
} else if (localName.equals("PRESSURE")) {
if ((state != INSIDE_DELTA_CHANNEL)
&& (state != INSIDE_STATUS_CHANNEL)) {
throw new SAXException(
localName
+ " element not expected here, state = "
+ state);
}
String amt = atts.getValue("AMOUNT");
this.output.write(Utils.stringToInt(amt));
this.getLogger().debug("PRESSURE - " + amt);
} else if (localName.equals("WHEEL")) {
if ((state != INSIDE_DELTA_CHANNEL)
&& (state != INSIDE_STATUS_CHANNEL)) {
throw new SAXException(
localName
+ " element not expected here, state = "
+ state);
}
String amt = atts.getValue("AMOUNT");
int a = Utils.stringToInt(amt);
int b = a;
int c = a;
b &= 127;
c >>= 7;
this.output.write(c);
this.output.write(b);
this.getLogger().debug(
"Wheel - " + a + ": (" + c + "," + a + ")");
} else if (localName.equals("EDATA")) {
if ((state != INSIDE_DELTA) && (state != INSIDE_STATUS)) {
throw new SAXException(
localName
+ " element not expected here, state = "
+ state);
}
buffer = new StringBuffer();
buffering = true;
this.getLogger().debug("EDATA (element, not text)");
} else if (
localName.equals("FORMAT")
|| localName.equals("TRACKS")
|| localName.equals("PPNQ")) {
if (state != INSIDE_MTHD) {
throw new SAXException(
localName
+ " element not expected here, state = "
+ state);
}
buffer = new StringBuffer();
buffering = true;
this.getLogger().debug(localName + " element");
} else {
this.getLogger().debug(
"Found " + localName + ", in state " + state);
}
} catch (ProcessingException e) {
throw new SAXException(e);
} catch (IOException e) {
throw new SAXException(e);
}
}
/* (non-Javadoc)
* @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
*/
public void endElement(String namespaceURI, String localName, String qName)
throws SAXException {
try {
if (localName.equals("CHUNK")) {
state = INSIDE_XMIDI;
} else if (localName.equals("MThd") || localName.equals("MTrk")) {
state = INSIDE_XMIDI;
} else if (localName.equals("DELTA")) {
state = INSIDE_MTRK;
} else if (localName.equals("STATUS")) {
state = INSIDE_DELTA;
} else if (localName.equals("CHANNEL")) {
if (state == INSIDE_STATUS_CHANNEL) {
state = INSIDE_STATUS;
} else if (state == INSIDE_DELTA_CHANNEL) {
state = INSIDE_DELTA;
}
} else if (localName.equals("EDATA")) {
if (!preventDataWrite) {
writeHex(buffer.toString(), expectedBytes);
this.getLogger().debug("EDATA: " + buffer.toString());
} else {
preventDataWrite = false;
}
buffering = false;
} else if (localName.equals("FORMAT")) {
String typ = buffer.toString();
for (int i = 0; i < typ.length(); i++) {
if (typ.substring(i, i + 1).compareTo("0")
< 0 || typ.substring(i, i + 1).compareTo("9")
> 0) {
throw new ProcessingException(
"Invalid numeric midi format: " + typ);
}
}
int midiFormat = Utils.stringToInt(typ);
writeHalfWord(midiFormat);
this.getLogger().debug("Format is " + midiFormat);
buffering = false;
} else if (localName.equals("TRACKS")) {
String sNum = buffer.toString();
Integer iNum = new Integer(sNum);
writeHalfWord(iNum.intValue());
this.getLogger().debug(iNum + " tracks");
buffering = false;
} else if (localName.equals("PPNQ")) {
String sPNQ = buffer.toString();
writeHex(sPNQ, 2);
this.getLogger().debug("PPNQ is " + sPNQ);
buffering = false;
} else if (localName.equals("HEXDATA")) {
writeHex(buffer.toString(), buffer.length() / 2);
buffering = false;
}
} catch (ProcessingException e) {
throw new SAXException(e);
} catch (IOException e) {
throw new SAXException(e);
}
}
void writeString(String s, int len)
throws IOException, ProcessingException {
int l = s.length();
if (l != len) {
throw new ProcessingException(
"writeString; string length ("
+ l
+ ") != expected length ("
+ len
+ ")");
}
this.output.write(s.getBytes());
}
void writeHex(String h, int len) throws ProcessingException, IOException {
int l = h.length();
int cnt = 0;
int bs = 0;
int bc = 0;
for (int i = 0; i < l; i++) {
String s = h.substring(i, i + 1);
int x = " \n\t\r".indexOf(s);
if (x != -1) {
continue;
}
int tmp = "0123456789ABCDEF".indexOf(s.toUpperCase());
if (bc == 0) {
bs = tmp;
} else if (bc == 1) {
bs <<= 4;
bs |= tmp;
} else {
throw new ProcessingException("writeHex; internal error");
}
bc++;
if (bc >= 2) {
this.output.write(bs);
cnt++;
bc = 0;
}
}
if (bc != 0) {
throw new ProcessingException("un-even number of hex digits");
}
if (cnt != len) {
throw new ProcessingException(
"writeHex count (" + cnt + ") != length (" + len + ")");
}
}
void writeFullWord(int f) throws ProcessingException, IOException {
byte[] b = Utils.intToBa(f, 4);
this.output.write(b);
}
void writeHalfWord(int h) throws ProcessingException, IOException {
byte[] b = Utils.intToBa(h, 2);
this.output.write(b);
}
/* (non-Javadoc)
* @see org.xml.sax.ContentHandler#characters(char[], int, int)
*/
public void characters(char[] str, int arg1, int arg2)
throws SAXException {
if (buffering) {
buffer.append(str, arg1, arg2);
}
}
}