/*
*
* Created on 16-dic-2005 TODO To change the template for this generated file go
* to Window - Preferences - Java - Code Style - Code Templates
*/
package org.herac.tuxguitar.io.tg.v10;
import java.awt.Color;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.log4j.Logger;
import org.herac.tuxguitar.io.base.TGFileFormat;
import org.herac.tuxguitar.io.base.TGFileFormatException;
import org.herac.tuxguitar.io.base.TGLocalFileExporter;
import org.herac.tuxguitar.song.models.TGBeat;
import org.herac.tuxguitar.song.models.TGChord;
import org.herac.tuxguitar.song.models.TGDivisionType;
import org.herac.tuxguitar.song.models.TGDuration;
import org.herac.tuxguitar.song.models.TGLyric;
import org.herac.tuxguitar.song.models.TGMarker;
import org.herac.tuxguitar.song.models.TGMeasure;
import org.herac.tuxguitar.song.models.TGMeasureHeader;
import org.herac.tuxguitar.song.models.TGNote;
import org.herac.tuxguitar.song.models.TGNoteEffect;
import org.herac.tuxguitar.song.models.TGSong;
import org.herac.tuxguitar.song.models.TGString;
import org.herac.tuxguitar.song.models.TGTempo;
import org.herac.tuxguitar.song.models.TGText;
import org.herac.tuxguitar.song.models.TGTimeSignature;
import org.herac.tuxguitar.song.models.TGTrack;
import org.herac.tuxguitar.song.models.TGVoice;
import org.herac.tuxguitar.song.models.effects.BendingEffect;
import org.herac.tuxguitar.song.models.effects.EffectPoint;
import org.herac.tuxguitar.song.models.effects.HarmonicEffect;
import org.herac.tuxguitar.song.models.effects.TGEffectGrace;
import org.herac.tuxguitar.song.models.effects.TGEffectTremoloPicking;
import org.herac.tuxguitar.song.models.effects.TGEffectTrill;
/**
* @author julian
*
* TODO To change the template for this generated type comment go to
* Window - Preferences - Java - Code Style - Code Templates
*/
public class TGOutputStream extends TGStream implements TGLocalFileExporter {
/** The Logger for this class. */
public static final transient Logger LOG = Logger
.getLogger(TGOutputStream.class);
public class TGVoiceJoiner {
// private TGFactory factory;
private TGMeasure measure;
public TGVoiceJoiner(TGMeasure measure) {
// this.factory = factory;
this.measure = measure.clone(measure.getHeader());
this.measure.setTrack(measure.getTrack());
}
public void joinBeats() {
TGBeat previous = null;
boolean finish = true;
long measureStart = this.measure.getStart();
long measureEnd = (measureStart + this.measure.getLength());
for (int i = 0; i < this.measure.countBeats(); i++) {
TGBeat beat = this.measure.getBeat(i);
TGVoice voice = beat.getVoice(0);
for (int v = 1; v < beat.countVoices(); v++) {
TGVoice currentVoice = beat.getVoice(v);
if (!currentVoice.isEmpty()) {
for (int n = 0; n < currentVoice.getNotes().size(); n++) {
TGNote note = currentVoice.getNote(n);
voice.addNote(note);
}
}
}
if (voice.isEmpty()) {
this.measure.removeBeat(beat);
finish = false;
break;
}
long beatStart = beat.getStart();
if (previous != null) {
long previousStart = previous.getStart();
TGDuration previousBestDuration = null;
for (int v = /* 1 */0; v < previous.countVoices(); v++) {
TGVoice previousVoice = previous.getVoice(v);
if (!previousVoice.isEmpty()) {
long length = previousVoice.getDuration().getTime();
if ((previousStart + length) <= beatStart) {
if (previousBestDuration == null
|| length > previousBestDuration.getTime()) {
previousBestDuration = previousVoice.getDuration();
}
}
}
}
if (previousBestDuration != null) {
previous.getVoice(0).setDuration(previousBestDuration.clone());
} else {
if (voice.isRestVoice()) {
this.measure.removeBeat(beat);
finish = false;
break;
}
TGDuration duration = TGDuration
.fromTime((beatStart - previousStart));
previous.getVoice(0).setDuration(duration.clone());
}
}
TGDuration beatBestDuration = null;
for (int v = /* 1 */0; v < beat.countVoices(); v++) {
TGVoice currentVoice = beat.getVoice(v);
if (!currentVoice.isEmpty()) {
long length = currentVoice.getDuration().getTime();
if ((beatStart + length) <= measureEnd) {
if (beatBestDuration == null
|| length > beatBestDuration.getTime()) {
beatBestDuration = currentVoice.getDuration();
}
}
}
}
if (beatBestDuration == null) {
if (voice.isRestVoice()) {
this.measure.removeBeat(beat);
finish = false;
break;
}
TGDuration duration = TGDuration.fromTime((measureEnd - beatStart));
voice.setDuration(duration.clone());
}
previous = beat;
}
if (!finish) {
joinBeats();
}
}
public void orderBeats() {
for (int i = 0; i < this.measure.countBeats(); i++) {
TGBeat minBeat = null;
for (int j = i; j < this.measure.countBeats(); j++) {
TGBeat beat = this.measure.getBeat(j);
if (minBeat == null || beat.getStart() < minBeat.getStart()) {
minBeat = beat;
}
}
this.measure.moveBeat(i, minBeat);
}
}
public TGMeasure process() {
this.orderBeats();
this.joinBeats();
return this.measure;
}
}
private DataOutputStream dataOutputStream;
public boolean configure(boolean setDefaults) {
return true;
}
public void exportSong(TGSong song) throws TGFileFormatException {
try {
this.writeVersion();
this.write(song);
this.dataOutputStream.flush();
this.dataOutputStream.close();
} catch (Throwable throwable) {
throw new TGFileFormatException(throwable);
}
}
public String getExportName() {
return "TuxGuitar 1.0";
}
public TGFileFormat getFileFormat() {
return new TGFileFormat("TuxGuitar", "*.tg");
}
public void init(OutputStream stream) {
this.dataOutputStream = new DataOutputStream(stream);
}
public boolean isSupportedExtension(String extension) {
return (extension.toLowerCase().equals(TG_FORMAT_EXTENSION));
}
private void write(TGSong song) {
// escribo el nombre
writeUnsignedByteString(song.getName());
// escribo el artista
writeUnsignedByteString(song.getArtist());
// escribo el album
writeUnsignedByteString(song.getAlbum());
// escribo el autor
writeUnsignedByteString(song.getAuthor());
// escribo la cantidad de measure headers
writeShort((short) song.countMeasureHeaders());
// escribo las pistas
TGMeasureHeader lastHeader = null;
for (final TGMeasureHeader header : song.getMeasureHeaders()) {
writeMeasureHeader(header, lastHeader);
lastHeader = header;
}
// escribo la cantidad de pistas
writeByte(song.countTracks());
// escribo las pistas
for (int i = 0; i < song.countTracks(); i++) {
TGTrack track = song.getTrack(i);
writeTrack(track);
}
}
private void writeBeat(TGBeat beat, TGBeatData data, boolean hasNext) {
TGVoice voice = beat.getVoice(0);
int header = hasNext ? BEAT_HAS_NEXT : 0;
// Berifico si hay cambio de duracion
if (!voice.getDuration().isEqual(data.getDuration())) {
header |= BEAT_NEXT_DURATION;
data.setDuration(voice.getDuration());
}
// Berifico si tiene notas
if (!beat.isRestBeat()) {
header |= BEAT_HAS_NOTES;
}
// Berifico si tiene acorde
if (beat.getChord() != null) {
header |= BEAT_HAS_CHORD;
}
// Berifico si tiene texto
if (beat.getText() != null) {
header |= BEAT_HAS_TEXT;
}
// escribo la cabecera
writeHeader(header);
// escribo la duracion
if (((header & BEAT_NEXT_DURATION) != 0)) {
writeDuration(voice.getDuration());
}
// escribo las notas
if (((header & BEAT_HAS_NOTES) != 0)) {
writeNotes(voice, data);
}
// escribo el acorde
if (((header & BEAT_HAS_CHORD) != 0)) {
writeChord(beat.getChord());
}
// escribo el texto
if (((header & BEAT_HAS_TEXT) != 0)) {
writeText(beat.getText());
}
}
private void writeBeats(TGMeasure measure, TGBeatData data) {
int count = measure.countBeats();
for (int i = 0; i < count; i++) {
TGBeat beat = measure.getBeat(i);
writeBeat(beat, data, (i + 1 < count));
}
}
private void writeBendEffect(BendingEffect effect) {
// escribo la cantidad de puntos
writeByte(effect.getPoints().size());
for (final EffectPoint point : effect.getPoints()) {
// escribo la posicion
writeByte(point.getPosition());
// escribo el valor
writeByte(point.getValue());
}
}
public void writeByte(int v) {
try {
this.dataOutputStream.write(v);
} catch (IOException e) {
LOG.error(e);
}
}
private void writeChannel(TGTrack track) {
int header = 0;
header = (track.isSolo()) ? header |= CHANNEL_SOLO : header;
header = (track.isMute()) ? header |= CHANNEL_MUTE : header;
writeHeader(header);
// escribo el canal
writeByte(track.getChannel().getChannel());
// escribo el canal de efectos
writeByte(track.getChannel().getEffectChannel());
// escribo el instrumento
writeByte(track.getChannel().getInstrument());
// escribo el volumen
writeByte(track.getChannel().getVolume());
// escribo el balance
writeByte(track.getChannel().getBalance());
// escribo el chorus
writeByte(track.getChannel().getChorus());
// escribo el reverb
writeByte(track.getChannel().getReverb());
// escribo el phaser
writeByte(track.getChannel().getPhaser());
// escribo el tremolo
writeByte(track.getChannel().getTremolo());
}
private void writeChord(TGChord chord) {
// escribo la cantidad de cuerdas
writeByte(chord.countStrings());
// escribo el nombre
writeUnsignedByteString(chord.getName());
// escribo el primer fret
writeByte(chord.getFirstFret());
// escribo el valor de cada cuerda
for (int string = 0; string < chord.countStrings(); string++) {
writeByte(chord.getFretValue(string));
}
}
private void writeDivisionType(TGDivisionType divisionType) {
// escribo los enters
writeByte(divisionType.getEnters());
// escribo los tiempos
writeByte(divisionType.getTimes());
}
private void writeDuration(TGDuration duration) {
int header = 0;
header = (duration.isDotted()) ? header |= DURATION_DOTTED : header;
header = (duration.isDoubleDotted()) ? header |= DURATION_DOUBLE_DOTTED
: header;
header = (!duration.getDivision().isEqual(TGDivisionType.NORMAL)) ? header |= DURATION_NO_TUPLE
: header;
writeHeader(header);
// escribo el valor
writeByte(duration.getValue());
// escribo el tipo de divisiones
if (((header & DURATION_NO_TUPLE) != 0)) {
writeDivisionType(duration.getDivision());
}
}
private void writeGraceEffect(TGEffectGrace effect) {
int header = 0;
header = (effect.isDead()) ? header |= GRACE_FLAG_DEAD : header;
header = (effect.isOnBeat()) ? header |= GRACE_FLAG_ON_BEAT : header;
// excribo el header
writeHeader(header);
// excribo el fret
writeByte(effect.getFret());
// excribo la duracion
writeByte(effect.getDuration());
// excribo el velocity
writeByte(effect.getDynamic());
// excribo la transicion
writeByte(effect.getTransition().getId());
}
private void writeHarmonicEffect(HarmonicEffect effect) {
// excribo el tipo
writeByte(effect.getId());
// excribo la data
if (!effect.equals(HarmonicEffect.NATURAL)){
writeByte(effect.getData());
}
}
public void writeHeader(int v) {
try {
this.dataOutputStream.write(v);
} catch (IOException e) {
LOG.error(e);
}
}
public void writeHeader(int v, int bCount) {
for (int i = bCount; i > 0; i--) {
writeHeader((v >>> ((8 * i) - 8)) & 0xFF);
}
}
private void writeInstrumentString(TGString string) {
// escribo el valor
writeByte(string.getValue());
}
private void writeIntegerString(String v) {
try {
this.dataOutputStream.writeInt(v.length());
this.dataOutputStream.writeChars(v);
} catch (IOException e) {
LOG.error(e);
}
}
private void writeLyrics(TGLyric lyrics) {
// escribo el compas de comienzo
writeShort((short) lyrics.getFrom());
// escribo el texto
writeIntegerString(lyrics.getLyrics());
}
private void writeMarker(TGMarker marker) {
// escribo el titulo
writeUnsignedByteString(marker.getTitle());
// escribo el color
writeRGBColor(marker.getColor());
}
private void writeMeasure(TGMeasure srcMeasure, TGMeasure lastMeasure) {
TGMeasure measure = new TGVoiceJoiner(srcMeasure).process();
int header = 0;
if (lastMeasure == null) {
header |= MEASURE_CLEF;
header |= MEASURE_KEYSIGNATURE;
} else {
// Clef
if (measure.getClef() != lastMeasure.getClef()) {
header |= MEASURE_CLEF;
}
// KeySignature
if (measure.getKeySignature() != lastMeasure.getKeySignature()) {
header |= MEASURE_KEYSIGNATURE;
}
}
// escribo la cabecera
writeHeader(header);
// escribo los beats
TGBeatData data = new TGBeatData(measure);
writeBeats(measure, data);
// escribo la clave
if (((header & MEASURE_CLEF) != 0)) {
switch (measure.getClef()) {
case ALTO:
writeByte(4);
break;
case BASS:
writeByte(2);
break;
case TENOR:
writeByte(3);
break;
case TREBLE:
writeByte(1);
break;
}
}
// escribo el key signature
if (((header & MEASURE_KEYSIGNATURE) != 0)) {
writeByte(measure.getKeySignature());
}
}
private void writeMeasureHeader(TGMeasureHeader measureheader,
TGMeasureHeader lastMeasureHeader) {
int header = 0;
if (lastMeasureHeader == null) {
header |= MEASURE_HEADER_TIMESIGNATURE;
header |= MEASURE_HEADER_TEMPO;
if (measureheader.getTripletFeel() != TGMeasureHeader.TRIPLET_FEEL_NONE) {
header |= MEASURE_HEADER_TRIPLET_FEEL;
}
} else {
// Time Signature
int numerator = measureheader.getTimeSignature().getNumerator();
int value = measureheader.getTimeSignature().getDenominator().getValue();
int prevNumerator = lastMeasureHeader.getTimeSignature().getNumerator();
int prevValue = lastMeasureHeader.getTimeSignature().getDenominator()
.getValue();
if (numerator != prevNumerator || value != prevValue) {
header |= MEASURE_HEADER_TIMESIGNATURE;
}
// Tempo
if (measureheader.getTempo().getValue() != lastMeasureHeader.getTempo()
.getValue()) {
header |= MEASURE_HEADER_TEMPO;
}
// Triplet Feel
if (measureheader.getTripletFeel() != lastMeasureHeader.getTripletFeel()) {
header |= MEASURE_HEADER_TRIPLET_FEEL;
}
}
header = (measureheader.isRepeatOpen()) ? header |= MEASURE_HEADER_REPEAT_OPEN
: header;
header = (measureheader.getRepeatClose() > 0) ? header |= MEASURE_HEADER_REPEAT_CLOSE
: header;
header = (measureheader.getRepeatAlternative() > 0) ? header |= MEASURE_HEADER_REPEAT_ALTERNATIVE
: header;
header = (measureheader.hasMarker()) ? header |= MEASURE_HEADER_MARKER
: header;
writeHeader(header);
// escribo el timeSignature
if (((header & MEASURE_HEADER_TIMESIGNATURE) != 0)) {
writeTimeSignature(measureheader.getTimeSignature());
}
// escribo el tempo
if (((header & MEASURE_HEADER_TEMPO) != 0)) {
writeTempo(measureheader.getTempo());
}
// escribo el numero de repeticiones
if (((header & MEASURE_HEADER_REPEAT_CLOSE) != 0)) {
writeShort((short) measureheader.getRepeatClose());
}
// escribo los finales alternativos
if (((header & MEASURE_HEADER_REPEAT_ALTERNATIVE) != 0)) {
writeByte(measureheader.getRepeatAlternative());
}
// escribo el marker
if (((header & MEASURE_HEADER_MARKER) != 0)) {
writeMarker(measureheader.getMarker());
}
// escribo el triplet feel
if (((header & MEASURE_HEADER_TRIPLET_FEEL) != 0)) {
writeByte(measureheader.getTripletFeel());
}
}
private void writeNote(int header, TGNote note) {
// escribo el valor
writeByte(note.getValue());
// escribo la cuerda
writeByte(note.getString());
// escribo el velocity
if (((header & NOTE_VELOCITY) != 0)) {
writeByte(note.getVelocity());
}
// escribo los efectos
if (((header & NOTE_EFFECT) != 0)) {
writeNoteEffect(note.getEffect());
}
}
private void writeNoteEffect(TGNoteEffect effect) {
int header = 0;
header = (effect.isBend()) ? header |= EFFECT_BEND : header;
header = (effect.isTremoloBar()) ? header |= EFFECT_TREMOLO_BAR : header;
header = (effect.isHarmonic()) ? header |= EFFECT_HARMONIC : header;
header = (effect.isGrace()) ? header |= EFFECT_GRACE : header;
header = (effect.isTrill()) ? header |= EFFECT_TRILL : header;
header = (effect.isTremoloPicking()) ? header |= EFFECT_TREMOLO_PICKING
: header;
header = (effect.isVibrato()) ? header |= EFFECT_VIBRATO : header;
header = (effect.isDeadNote()) ? header |= EFFECT_DEAD : header;
header = (effect.isSlide()) ? header |= EFFECT_SLIDE : header;
header = (effect.isHammer()) ? header |= EFFECT_HAMMER : header;
header = (effect.isGhostNote()) ? header |= EFFECT_GHOST : header;
header = (effect.isAccentuatedNote()) ? header |= EFFECT_ACCENTUATED
: header;
header = (effect.isHeavyAccentuatedNote()) ? header |= EFFECT_HEAVY_ACCENTUATED
: header;
header = (effect.isPalmMute()) ? header |= EFFECT_PALM_MUTE : header;
header = (effect.isStaccato()) ? header |= EFFECT_STACCATO : header;
header = (effect.isTapping()) ? header |= EFFECT_TAPPING : header;
header = (effect.isSlapping()) ? header |= EFFECT_SLAPPING : header;
header = (effect.isPopping()) ? header |= EFFECT_POPPING : header;
header = (effect.isFadeIn()) ? header |= EFFECT_FADE_IN : header;
writeHeader(header, 3);
// escribo el bend
if (((header & EFFECT_BEND) != 0)) {
writeBendEffect(effect.getBend());
}
// leo el tremolo bar
if (((header & EFFECT_TREMOLO_BAR) != 0)) {
writeTremoloBarEffect(effect.getTremoloBar());
}
// leo el harmonic
if (((header & EFFECT_HARMONIC) != 0)) {
writeHarmonicEffect(effect.getHarmonic());
}
// leo el grace
if (((header & EFFECT_GRACE) != 0)) {
writeGraceEffect(effect.getGrace());
}
// leo el trill
if (((header & EFFECT_TRILL) != 0)) {
writeTrillEffect(effect.getTrill());
}
// leo el tremolo picking
if (((header & EFFECT_TREMOLO_PICKING) != 0)) {
writeTremoloPickingEffect(effect.getTremoloPicking());
}
}
private void writeNotes(TGVoice voice, TGBeatData data) {
for (int i = 0; i < voice.getNotes().size(); i++) {
TGNote note = voice.getNote(i);
int header = (i + 1 < voice.getNotes().size() ? NOTE_HAS_NEXT : 0);
header = (note.isTiedNote()) ? header |= NOTE_TIED : header;
if (note.getVelocity() != data.getVelocity()) {
data.setVelocity(note.getVelocity());
header |= NOTE_VELOCITY;
}
header = (note.getEffect().hasAnyEffect()) ? header |= NOTE_EFFECT
: header;
writeHeader(header);
writeNote(header, note);
}
}
private void writeRGBColor(Color color) {
// escribo el RGB
writeByte(color.getRed());
writeByte(color.getGreen());
writeByte(color.getBlue());
}
public void writeShort(short v) {
try {
this.dataOutputStream.writeShort(v);
} catch (IOException e) {
LOG.error(e);
}
}
private void writeTempo(TGTempo tempo) {
// escribo el valor
writeShort((short) tempo.getValue());
}
private void writeText(TGText text) {
// escribo el texto
writeUnsignedByteString(text.getValue());
}
private void writeTimeSignature(TGTimeSignature timeSignature) {
// escribo el numerador
writeByte(timeSignature.getNumerator());
// escribo el denominador
writeDuration(timeSignature.getDenominator());
}
private void writeTrack(TGTrack track) {
// header
int header = 0;
if (!track.getLyrics().getLyrics().isEmpty()) {
header |= TRACK_LYRICS;
}
writeHeader(header);
// escribo el nombre
writeUnsignedByteString(track.getName());
// escribo el canal
writeChannel(track);
// escribo los compases
TGMeasure lastMeasure = null;
for (final TGMeasure measure : track.getMeasures()) {
writeMeasure(measure, lastMeasure);
lastMeasure = measure;
}
// escribo la cantidad de cuerdas
writeByte(track.getStrings().size());
// escribo las cuerdas
for (final TGString string : track.getStrings()) {
writeInstrumentString(string);
}
// escribo el offset
writeByte(track.getOffset() - TGTrack.MIN_OFFSET);
// escribo el color
writeRGBColor(track.getColor());
// escribo el lyrics
if (((header & TRACK_LYRICS) != 0)) {
writeLyrics(track.getLyrics());
}
}
private void writeTremoloBarEffect(BendingEffect effect) {
// escribo la cantidad de puntos
writeByte(effect.getPoints().size());
for (final EffectPoint point : effect.getPoints()) {
// escribo la posicion
writeByte(point.getPosition());
// escribo el valor
writeByte((point.getValue() + EffectPoint.MAX_VALUE_LENGTH));
}
}
private void writeTremoloPickingEffect(TGEffectTremoloPicking effect) {
// excribo la duracion
writeByte(effect.getDuration().getValue());
}
private void writeTrillEffect(TGEffectTrill effect) {
// excribo el fret
writeByte(effect.getFret());
// excribo la duracion
writeByte(effect.getDuration().getValue());
}
private void writeUnsignedByteString(String v) {
try {
String byteString = (v == null ? new String() : ((v.length() > 0xFF) ? v
.substring(0, 0xFF) : v));
this.dataOutputStream.write(byteString.length());
this.dataOutputStream.writeChars(byteString);
} catch (IOException e) {
LOG.error(e);
}
}
private void writeVersion() {
writeUnsignedByteString(TG_FORMAT_VERSION);
}
}