author | never |
Mon, 12 Jul 2010 22:27:18 -0700 | |
changeset 5926 | a36f90d986b6 |
parent 5506 | 202f599c92aa |
child 18215 | b2afd66ce6db |
permissions | -rw-r--r-- |
2 | 1 |
/* |
5506 | 2 |
* Copyright (c) 1999, 2009, Oracle and/or its affiliates. All rights reserved. |
2 | 3 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 |
* |
|
5 |
* This code is free software; you can redistribute it and/or modify it |
|
6 |
* under the terms of the GNU General Public License version 2 only, as |
|
5506 | 7 |
* published by the Free Software Foundation. Oracle designates this |
2 | 8 |
* particular file as subject to the "Classpath" exception as provided |
5506 | 9 |
* by Oracle in the LICENSE file that accompanied this code. |
2 | 10 |
* |
11 |
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 |
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 |
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 |
* version 2 for more details (a copy is included in the LICENSE file that |
|
15 |
* accompanied this code). |
|
16 |
* |
|
17 |
* You should have received a copy of the GNU General Public License version |
|
18 |
* 2 along with this work; if not, write to the Free Software Foundation, |
|
19 |
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 |
* |
|
5506 | 21 |
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
22 |
* or visit www.oracle.com if you need additional information or have any |
|
23 |
* questions. |
|
2 | 24 |
*/ |
25 |
||
26 |
package com.sun.media.sound; |
|
27 |
||
28 |
import java.io.DataInputStream; |
|
29 |
import java.io.DataOutputStream; |
|
30 |
import java.io.PipedInputStream; |
|
31 |
import java.io.PipedOutputStream; |
|
32 |
import java.io.ByteArrayOutputStream; |
|
33 |
import java.io.ByteArrayInputStream; |
|
34 |
import java.io.SequenceInputStream; |
|
35 |
import java.io.File; |
|
36 |
import java.io.FileOutputStream; |
|
37 |
import java.io.BufferedOutputStream; |
|
38 |
import java.io.InputStream; |
|
39 |
import java.io.IOException; |
|
40 |
import java.lang.IllegalArgumentException; |
|
41 |
import java.io.OutputStream; |
|
42 |
import java.util.Vector; |
|
43 |
||
44 |
import javax.sound.midi.MidiFileFormat; |
|
45 |
import javax.sound.midi.InvalidMidiDataException; |
|
46 |
import javax.sound.midi.MidiEvent; |
|
47 |
import javax.sound.midi.MetaMessage; |
|
48 |
import javax.sound.midi.MidiMessage; |
|
49 |
import javax.sound.midi.Sequence; |
|
50 |
import javax.sound.midi.ShortMessage; |
|
51 |
import javax.sound.midi.SysexMessage; |
|
52 |
import javax.sound.midi.Track; |
|
53 |
import javax.sound.midi.spi.MidiFileWriter; |
|
54 |
||
55 |
||
56 |
/** |
|
57 |
* MIDI file writer. |
|
58 |
* |
|
59 |
* @author Kara Kytle |
|
60 |
* @author Jan Borgersen |
|
61 |
*/ |
|
62 |
public class StandardMidiFileWriter extends MidiFileWriter { |
|
63 |
||
64 |
private static final int MThd_MAGIC = 0x4d546864; // 'MThd' |
|
65 |
private static final int MTrk_MAGIC = 0x4d54726b; // 'MTrk' |
|
66 |
||
67 |
private static final int ONE_BYTE = 1; |
|
68 |
private static final int TWO_BYTE = 2; |
|
69 |
private static final int SYSEX = 3; |
|
70 |
private static final int META = 4; |
|
71 |
private static final int ERROR = 5; |
|
72 |
private static final int IGNORE = 6; |
|
73 |
||
74 |
private static final int MIDI_TYPE_0 = 0; |
|
75 |
private static final int MIDI_TYPE_1 = 1; |
|
76 |
||
77 |
private static final int bufferSize = 16384; // buffersize for write |
|
78 |
private DataOutputStream tddos; // data output stream for track writing |
|
79 |
||
80 |
||
81 |
||
82 |
/** |
|
83 |
* MIDI parser types |
|
84 |
*/ |
|
3451
22d903fd3c5e
6657625: RmfFileReader/StandardMidiFileWriter.types are public mutable statics (findbugs)
amenkov
parents:
2
diff
changeset
|
85 |
private static final int types[] = { |
2 | 86 |
MIDI_TYPE_0, |
87 |
MIDI_TYPE_1 |
|
88 |
}; |
|
89 |
||
90 |
||
91 |
/** |
|
92 |
* new |
|
93 |
*/ |
|
94 |
public int[] getMidiFileTypes() { |
|
95 |
int[] localArray = new int[types.length]; |
|
96 |
System.arraycopy(types, 0, localArray, 0, types.length); |
|
97 |
return localArray; |
|
98 |
} |
|
99 |
||
100 |
/** |
|
101 |
* Obtains the file types that this provider can write from the |
|
102 |
* sequence specified. |
|
103 |
* @param sequence the sequence for which midi file type support |
|
104 |
* is queried |
|
105 |
* @return array of file types. If no file types are supported, |
|
106 |
* returns an array of length 0. |
|
107 |
*/ |
|
108 |
public int[] getMidiFileTypes(Sequence sequence){ |
|
109 |
int typesArray[]; |
|
110 |
Track tracks[] = sequence.getTracks(); |
|
111 |
||
112 |
if( tracks.length==1 ) { |
|
113 |
typesArray = new int[2]; |
|
114 |
typesArray[0] = MIDI_TYPE_0; |
|
115 |
typesArray[1] = MIDI_TYPE_1; |
|
116 |
} else { |
|
117 |
typesArray = new int[1]; |
|
118 |
typesArray[0] = MIDI_TYPE_1; |
|
119 |
} |
|
120 |
||
121 |
return typesArray; |
|
122 |
} |
|
123 |
||
124 |
public boolean isFileTypeSupported(int type) { |
|
125 |
for(int i=0; i<types.length; i++) { |
|
126 |
if( type == types[i] ) { |
|
127 |
return true; |
|
128 |
} |
|
129 |
} |
|
130 |
return false; |
|
131 |
} |
|
132 |
||
133 |
public int write(Sequence in, int type, OutputStream out) throws IOException { |
|
134 |
byte [] buffer = null; |
|
135 |
||
136 |
int bytesRead = 0; |
|
137 |
long bytesWritten = 0; |
|
138 |
||
139 |
if( !isFileTypeSupported(type,in) ) { |
|
140 |
throw new IllegalArgumentException("Could not write MIDI file"); |
|
141 |
} |
|
142 |
// First get the fileStream from this sequence |
|
143 |
InputStream fileStream = getFileStream(type,in); |
|
144 |
if (fileStream == null) { |
|
145 |
throw new IllegalArgumentException("Could not write MIDI file"); |
|
146 |
} |
|
147 |
buffer = new byte[bufferSize]; |
|
148 |
||
149 |
while( (bytesRead = fileStream.read( buffer )) >= 0 ) { |
|
150 |
out.write( buffer, 0, (int)bytesRead ); |
|
151 |
bytesWritten += bytesRead; |
|
152 |
} |
|
153 |
// Done....return bytesWritten |
|
154 |
return (int) bytesWritten; |
|
155 |
} |
|
156 |
||
157 |
public int write(Sequence in, int type, File out) throws IOException { |
|
158 |
FileOutputStream fos = new FileOutputStream(out); // throws IOException |
|
159 |
int bytesWritten = write( in, type, fos ); |
|
160 |
fos.close(); |
|
161 |
return bytesWritten; |
|
162 |
} |
|
163 |
||
164 |
//================================================================================= |
|
165 |
||
166 |
||
167 |
private InputStream getFileStream(int type, Sequence sequence) throws IOException { |
|
168 |
Track tracks[] = sequence.getTracks(); |
|
169 |
int bytesBuilt = 0; |
|
170 |
int headerLength = 14; |
|
171 |
int length = 0; |
|
172 |
int timeFormat; |
|
173 |
float divtype; |
|
174 |
||
175 |
PipedOutputStream hpos = null; |
|
176 |
DataOutputStream hdos = null; |
|
177 |
PipedInputStream headerStream = null; |
|
178 |
||
179 |
InputStream trackStreams [] = null; |
|
180 |
InputStream trackStream = null; |
|
181 |
InputStream fStream = null; |
|
182 |
||
183 |
// Determine the filetype to write |
|
184 |
if( type==MIDI_TYPE_0 ) { |
|
185 |
if (tracks.length != 1) { |
|
186 |
return null; |
|
187 |
} |
|
188 |
} else if( type==MIDI_TYPE_1 ) { |
|
189 |
if (tracks.length < 1) { // $$jb: 05.31.99: we _can_ write TYPE_1 if tracks.length==1 |
|
190 |
return null; |
|
191 |
} |
|
192 |
} else { |
|
193 |
if(tracks.length==1) { |
|
194 |
type = MIDI_TYPE_0; |
|
195 |
} else if(tracks.length>1) { |
|
196 |
type = MIDI_TYPE_1; |
|
197 |
} else { |
|
198 |
return null; |
|
199 |
} |
|
200 |
} |
|
201 |
||
202 |
// Now build the file one track at a time |
|
203 |
// Note that above we made sure that MIDI_TYPE_0 only happens |
|
204 |
// if tracks.length==1 |
|
205 |
||
206 |
trackStreams = new InputStream[tracks.length]; |
|
207 |
int trackCount = 0; |
|
208 |
for(int i=0; i<tracks.length; i++) { |
|
209 |
try { |
|
210 |
trackStreams[trackCount] = writeTrack( tracks[i], type ); |
|
211 |
trackCount++; |
|
212 |
} catch (InvalidMidiDataException e) { |
|
213 |
if(Printer.err) Printer.err("Exception in write: " + e.getMessage()); |
|
214 |
} |
|
215 |
//bytesBuilt += trackStreams[i].getLength(); |
|
216 |
} |
|
217 |
||
218 |
// Now seqence the track streams |
|
219 |
if( trackCount == 1 ) { |
|
220 |
trackStream = trackStreams[0]; |
|
221 |
} else if( trackCount > 1 ){ |
|
222 |
trackStream = trackStreams[0]; |
|
223 |
for(int i=1; i<tracks.length; i++) { |
|
224 |
// fix for 5048381: NullPointerException when saving a MIDI sequence |
|
225 |
// don't include failed track streams |
|
226 |
if (trackStreams[i] != null) { |
|
227 |
trackStream = new SequenceInputStream( trackStream, trackStreams[i]); |
|
228 |
} |
|
229 |
} |
|
230 |
} else { |
|
231 |
throw new IllegalArgumentException("invalid MIDI data in sequence"); |
|
232 |
} |
|
233 |
||
234 |
// Now build the header... |
|
235 |
hpos = new PipedOutputStream(); |
|
236 |
hdos = new DataOutputStream(hpos); |
|
237 |
headerStream = new PipedInputStream(hpos); |
|
238 |
||
239 |
// Write the magic number |
|
240 |
hdos.writeInt( MThd_MAGIC ); |
|
241 |
||
242 |
// Write the header length |
|
243 |
hdos.writeInt( headerLength - 8 ); |
|
244 |
||
245 |
// Write the filetype |
|
246 |
if(type==MIDI_TYPE_0) { |
|
247 |
hdos.writeShort( 0 ); |
|
248 |
} else { |
|
249 |
// MIDI_TYPE_1 |
|
250 |
hdos.writeShort( 1 ); |
|
251 |
} |
|
252 |
||
253 |
// Write the number of tracks |
|
254 |
hdos.writeShort( (short) trackCount ); |
|
255 |
||
256 |
// Determine and write the timing format |
|
257 |
divtype = sequence.getDivisionType(); |
|
258 |
if( divtype == Sequence.PPQ ) { |
|
259 |
timeFormat = sequence.getResolution(); |
|
260 |
} else if( divtype == Sequence.SMPTE_24) { |
|
261 |
timeFormat = (24<<8) * -1; |
|
262 |
timeFormat += (sequence.getResolution() & 0xFF); |
|
263 |
} else if( divtype == Sequence.SMPTE_25) { |
|
264 |
timeFormat = (25<<8) * -1; |
|
265 |
timeFormat += (sequence.getResolution() & 0xFF); |
|
266 |
} else if( divtype == Sequence.SMPTE_30DROP) { |
|
267 |
timeFormat = (29<<8) * -1; |
|
268 |
timeFormat += (sequence.getResolution() & 0xFF); |
|
269 |
} else if( divtype == Sequence.SMPTE_30) { |
|
270 |
timeFormat = (30<<8) * -1; |
|
271 |
timeFormat += (sequence.getResolution() & 0xFF); |
|
272 |
} else { |
|
273 |
// $$jb: 04.08.99: What to really do here? |
|
274 |
return null; |
|
275 |
} |
|
276 |
hdos.writeShort( timeFormat ); |
|
277 |
||
278 |
// now construct an InputStream to become the FileStream |
|
279 |
fStream = new SequenceInputStream(headerStream, trackStream); |
|
280 |
hdos.close(); |
|
281 |
||
282 |
length = bytesBuilt + headerLength; |
|
283 |
return fStream; |
|
284 |
} |
|
285 |
||
286 |
/** |
|
287 |
* Returns ONE_BYTE, TWO_BYTE, SYSEX, META, |
|
288 |
* ERROR, or IGNORE (i.e. invalid for a MIDI file) |
|
289 |
*/ |
|
290 |
private int getType(int byteValue) { |
|
291 |
if ((byteValue & 0xF0) == 0xF0) { |
|
292 |
switch(byteValue) { |
|
293 |
case 0xF0: |
|
294 |
case 0xF7: |
|
295 |
return SYSEX; |
|
296 |
case 0xFF: |
|
297 |
return META; |
|
298 |
} |
|
299 |
return IGNORE; |
|
300 |
} |
|
301 |
||
302 |
switch(byteValue & 0xF0) { |
|
303 |
case 0x80: |
|
304 |
case 0x90: |
|
305 |
case 0xA0: |
|
306 |
case 0xB0: |
|
307 |
case 0xE0: |
|
308 |
return TWO_BYTE; |
|
309 |
case 0xC0: |
|
310 |
case 0xD0: |
|
311 |
return ONE_BYTE; |
|
312 |
} |
|
313 |
return ERROR; |
|
314 |
} |
|
315 |
||
316 |
private final static long mask = 0x7F; |
|
317 |
||
318 |
private int writeVarInt(long value) throws IOException { |
|
319 |
int len = 1; |
|
320 |
int shift=63; // number of bitwise left-shifts of mask |
|
321 |
// first screen out leading zeros |
|
322 |
while ((shift > 0) && ((value & (mask << shift)) == 0)) shift-=7; |
|
323 |
// then write actual values |
|
324 |
while (shift > 0) { |
|
325 |
tddos.writeByte((int) (((value & (mask << shift)) >> shift) | 0x80)); |
|
326 |
shift-=7; |
|
327 |
len++; |
|
328 |
} |
|
329 |
tddos.writeByte((int) (value & mask)); |
|
330 |
return len; |
|
331 |
} |
|
332 |
||
333 |
private InputStream writeTrack( Track track, int type ) throws IOException, InvalidMidiDataException { |
|
334 |
int bytesWritten = 0; |
|
335 |
int lastBytesWritten = 0; |
|
336 |
int size = track.size(); |
|
337 |
PipedOutputStream thpos = new PipedOutputStream(); |
|
338 |
DataOutputStream thdos = new DataOutputStream(thpos); |
|
339 |
PipedInputStream thpis = new PipedInputStream(thpos); |
|
340 |
||
341 |
ByteArrayOutputStream tdbos = new ByteArrayOutputStream(); |
|
342 |
tddos = new DataOutputStream(tdbos); |
|
343 |
ByteArrayInputStream tdbis = null; |
|
344 |
||
345 |
SequenceInputStream fStream = null; |
|
346 |
||
347 |
long currentTick = 0; |
|
348 |
long deltaTick = 0; |
|
349 |
long eventTick = 0; |
|
350 |
int runningStatus = -1; |
|
351 |
||
352 |
// ----------------------------- |
|
353 |
// Write each event in the track |
|
354 |
// ----------------------------- |
|
355 |
for(int i=0; i<size; i++) { |
|
356 |
MidiEvent event = track.get(i); |
|
357 |
||
358 |
int status; |
|
359 |
int eventtype; |
|
360 |
int metatype; |
|
361 |
int data1, data2; |
|
362 |
int length; |
|
363 |
byte data[] = null; |
|
364 |
ShortMessage shortMessage = null; |
|
365 |
MetaMessage metaMessage = null; |
|
366 |
SysexMessage sysexMessage = null; |
|
367 |
||
368 |
// get the tick |
|
369 |
// $$jb: this gets easier if we change all system-wide time to delta ticks |
|
370 |
eventTick = event.getTick(); |
|
371 |
deltaTick = event.getTick() - currentTick; |
|
372 |
currentTick = event.getTick(); |
|
373 |
||
374 |
// get the status byte |
|
375 |
status = event.getMessage().getStatus(); |
|
376 |
eventtype = getType( status ); |
|
377 |
||
378 |
switch( eventtype ) { |
|
379 |
case ONE_BYTE: |
|
380 |
shortMessage = (ShortMessage) event.getMessage(); |
|
381 |
data1 = shortMessage.getData1(); |
|
382 |
bytesWritten += writeVarInt( deltaTick ); |
|
383 |
||
384 |
if(status!=runningStatus) { |
|
385 |
runningStatus=status; |
|
386 |
tddos.writeByte(status); bytesWritten += 1; |
|
387 |
} |
|
388 |
tddos.writeByte(data1); bytesWritten += 1; |
|
389 |
break; |
|
390 |
||
391 |
case TWO_BYTE: |
|
392 |
shortMessage = (ShortMessage) event.getMessage(); |
|
393 |
data1 = shortMessage.getData1(); |
|
394 |
data2 = shortMessage.getData2(); |
|
395 |
||
396 |
bytesWritten += writeVarInt( deltaTick ); |
|
397 |
if(status!=runningStatus) { |
|
398 |
runningStatus=status; |
|
399 |
tddos.writeByte(status); bytesWritten += 1; |
|
400 |
} |
|
401 |
tddos.writeByte(data1); bytesWritten += 1; |
|
402 |
tddos.writeByte(data2); bytesWritten += 1; |
|
403 |
break; |
|
404 |
||
405 |
case SYSEX: |
|
406 |
sysexMessage = (SysexMessage) event.getMessage(); |
|
407 |
length = sysexMessage.getLength(); |
|
408 |
data = sysexMessage.getMessage(); |
|
409 |
bytesWritten += writeVarInt( deltaTick ); |
|
410 |
||
411 |
// $$jb: 04.08.99: always write status for sysex |
|
412 |
runningStatus=status; |
|
413 |
tddos.writeByte( data[0] ); bytesWritten += 1; |
|
414 |
||
415 |
// $$jb: 10.18.99: we don't maintain length in |
|
416 |
// the message data for SysEx (it is not transmitted |
|
417 |
// over the line), so write the calculated length |
|
418 |
// minus the status byte |
|
419 |
bytesWritten += writeVarInt( (data.length-1) ); |
|
420 |
||
421 |
// $$jb: 10.18.99: now write the rest of the |
|
422 |
// message |
|
423 |
tddos.write(data, 1, (data.length-1)); |
|
424 |
bytesWritten += (data.length-1); |
|
425 |
break; |
|
426 |
||
427 |
case META: |
|
428 |
metaMessage = (MetaMessage) event.getMessage(); |
|
429 |
length = metaMessage.getLength(); |
|
430 |
data = metaMessage.getMessage(); |
|
431 |
bytesWritten += writeVarInt( deltaTick ); |
|
432 |
||
433 |
// $$jb: 10.18.99: getMessage() returns the |
|
434 |
// entire valid midi message for a file, |
|
435 |
// including the status byte and the var-length-int |
|
436 |
// length value, so we can just write the data |
|
437 |
// here. note that we must _always_ write the |
|
438 |
// status byte, regardless of runningStatus. |
|
439 |
runningStatus=status; |
|
440 |
tddos.write( data, 0, data.length ); |
|
441 |
bytesWritten += data.length; |
|
442 |
break; |
|
443 |
||
444 |
case IGNORE: |
|
445 |
// ignore this event |
|
446 |
break; |
|
447 |
||
448 |
case ERROR: |
|
449 |
// ignore this event |
|
450 |
break; |
|
451 |
||
452 |
default: |
|
453 |
throw new InvalidMidiDataException("internal file writer error"); |
|
454 |
} |
|
455 |
} |
|
456 |
// --------------------------------- |
|
457 |
// End write each event in the track |
|
458 |
// --------------------------------- |
|
459 |
||
460 |
// Build Track header now that we know length |
|
461 |
thdos.writeInt(MTrk_MAGIC); |
|
462 |
thdos.writeInt(bytesWritten); |
|
463 |
bytesWritten += 8; |
|
464 |
||
465 |
// Now sequence them |
|
466 |
tdbis = new ByteArrayInputStream( tdbos.toByteArray() ); |
|
467 |
fStream = new SequenceInputStream(thpis,tdbis); |
|
468 |
thdos.close(); |
|
469 |
tddos.close(); |
|
470 |
||
471 |
return fStream; |
|
472 |
} |
|
473 |
} |