package com.xenoage.zong.desktop.io.wav.out;
/*
* Copyright (c) 2007 by Karl Helgason All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this list of conditions and the following
* disclaimer. - 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.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 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.
*/
import com.sun.media.sound.AudioSynthesizer;
import lombok.val;
import javax.sound.midi.*;
import javax.sound.midi.MidiDevice.Info;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Set;
/**
* Converts a Midi {@link Sequence} to a sampled audio file (.wav) stream.
*
* Based on code from the Gervill project, see
* the above copyright notice.
*
* @author Andreas Wenger
*/
public class MidiToWaveRenderer {
/**
* Render sequence using selected or default soundbank into wave audio file.
* If activeTracks is not null, only the tracks with the given indices are rendered.
*/
public static void render(Soundbank soundbank, Sequence sequence, Set<Integer> activeTracks,
OutputStream wavOutputStream)
throws IOException, MidiUnavailableException {
// Find available AudioSynthesizer.
AudioSynthesizer synth = findAudioSynthesizer();
if (synth == null) {
System.out.println("No AudioSynhtesizer was found!");
System.exit(1);
}
// Open AudioStream from AudioSynthesizer.
AudioInputStream stream = synth.openStream(null, null);
// Load user-selected Soundbank into AudioSynthesizer.
if (soundbank != null) {
Soundbank defsbk = synth.getDefaultSoundbank();
if (defsbk != null)
synth.unloadAllInstruments(defsbk);
synth.loadAllInstruments(soundbank);
}
// Play Sequence into AudioSynthesizer Receiver.
double total = send(sequence, activeTracks, synth.getReceiver());
// Calculate how long the WAVE file needs to be.
long len = (long) (stream.getFormat().getFrameRate() * (total + 4));
stream = new AudioInputStream(stream, stream.getFormat(), len);
// Write WAVE file to disk.
AudioSystem.write(stream, AudioFileFormat.Type.WAVE, wavOutputStream);
// We are finished, close synthesizer.
synth.close();
}
/*
* Find available AudioSynthesizer.
*/
public static AudioSynthesizer findAudioSynthesizer()
throws MidiUnavailableException {
// First check if default synthesizer is AudioSynthesizer.
Synthesizer synth = MidiSystem.getSynthesizer();
if (synth instanceof AudioSynthesizer)
return (AudioSynthesizer) synth;
// If default synhtesizer is not AudioSynthesizer, check others.
Info[] infos = MidiSystem.getMidiDeviceInfo();
for (val info : infos) {
MidiDevice dev = MidiSystem.getMidiDevice(info);
if (dev instanceof AudioSynthesizer)
return (AudioSynthesizer) dev;
}
// No AudioSynthesizer was found, return null.
return null;
}
/**
* Send entry MIDI Sequence into Receiver using timestamps.
* If activeTracks is not null, only the tracks with the given indices are rendered.
*/
private static double send(Sequence seq, Set<Integer> activeTracks, Receiver recv) {
float divtype = seq.getDivisionType();
assert (seq.getDivisionType() == Sequence.PPQ);
Track[] tracks = seq.getTracks();
int[] trackspos = new int[tracks.length];
int mpq = 500000;
int seqres = seq.getResolution();
long lasttick = 0;
long curtime = 0;
while (true) {
MidiEvent selevent = null;
int seltrack = -1;
for (int i = 0; i < tracks.length; i++) {
if (activeTracks != null && !activeTracks.contains(i))
continue;
int trackpos = trackspos[i];
Track track = tracks[i];
if (trackpos < track.size()) {
MidiEvent event = track.get(trackpos);
if (selevent == null || event.getTick() < selevent.getTick()) {
selevent = event;
seltrack = i;
}
}
}
if (seltrack == -1)
break;
trackspos[seltrack]++;
long tick = selevent.getTick();
if (divtype == Sequence.PPQ)
curtime += ((tick - lasttick) * mpq) / seqres;
else
curtime = (long) ((tick * 1000000.0 * divtype) / seqres);
lasttick = tick;
MidiMessage msg = selevent.getMessage();
if (msg instanceof MetaMessage) {
if (divtype == Sequence.PPQ)
if (((MetaMessage) msg).getType() == 0x51) {
byte[] data = ((MetaMessage) msg).getData();
mpq = ((data[0] & 0xff) << 16) | ((data[1] & 0xff) << 8) | (data[2] & 0xff);
}
}
else {
if (recv != null)
recv.send(msg, curtime);
}
}
return curtime / 1000000.0;
}
}