/*
* 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.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
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.factory.TGFactory;
import org.herac.tuxguitar.song.models.TGBeat;
import org.herac.tuxguitar.song.models.TGChord;
import org.herac.tuxguitar.song.models.TGColor;
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.TGDivisionType;
import org.herac.tuxguitar.song.models.TGVoice;
import org.herac.tuxguitar.song.models.effects.TGEffectBend;
import org.herac.tuxguitar.song.models.effects.TGEffectGrace;
import org.herac.tuxguitar.song.models.effects.TGEffectHarmonic;
import org.herac.tuxguitar.song.models.effects.TGEffectTremoloBar;
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{
private DataOutputStream dataOutputStream;
public boolean isSupportedExtension(String extension) {
return (extension.toLowerCase().equals(TG_FORMAT_EXTENSION));
}
public String getExportName(){
return "TuxGuitar 1.0";
}
public TGFileFormat getFileFormat(){
return new TGFileFormat("TuxGuitar","*.tg");
}
public boolean configure(boolean setDefaults){
return true;
}
public void init(TGFactory factory,OutputStream stream){
this.dataOutputStream = new DataOutputStream(stream);
}
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);
}
}
private void writeVersion(){
writeUnsignedByteString(TG_FORMAT_VERSION);
}
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;
Iterator headers = song.getMeasureHeaders();
while(headers.hasNext()){
TGMeasureHeader header = (TGMeasureHeader)headers.next();
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 writeTrack(TGTrack track){
//header
int header = 0;
if(!track.getLyrics().isEmpty()){
header |= TRACK_LYRICS;
}
writeHeader(header);
//escribo el nombre
writeUnsignedByteString(track.getName());
//escribo el canal
writeChannel(track);
//escribo los compases
TGMeasure lastMeasure = null;
Iterator measures = track.getMeasures();
while(measures.hasNext()){
TGMeasure measure = (TGMeasure)measures.next();
writeMeasure(measure,lastMeasure);
lastMeasure = measure;
}
//escribo la cantidad de cuerdas
writeByte(track.getStrings().size());
//escribo las cuerdas
Iterator stringIt = track.getStrings().iterator();
while(stringIt.hasNext()){
TGString string = (TGString)stringIt.next();
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 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 writeMeasure(TGMeasure srcMeasure,TGMeasure lastMeasure){
TGMeasure measure = new TGVoiceJoiner(new TGFactory(),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)){
writeByte(measure.getClef());
}
//escribo el key signature
if(((header & MEASURE_KEYSIGNATURE) != 0)){
writeByte(measure.getKeySignature());
}
}
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 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 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 writeNotes(TGVoice voice,TGBeatData data){
for( int i = 0 ; i < voice.countNotes() ; i ++){
TGNote note = voice.getNote(i);
int header = ( i + 1 < voice.countNotes() ? 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 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 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 writeText(TGText text){
//escribo el texto
writeUnsignedByteString(text.getValue());
}
private void writeInstrumentString(TGString string){
//escribo el valor
writeByte(string.getValue());
}
private void writeTempo(TGTempo tempo){
//escribo el valor
writeShort((short)tempo.getValue());
}
private void writeTimeSignature(TGTimeSignature timeSignature){
//escribo el numerador
writeByte(timeSignature.getNumerator());
//escribo el denominador
writeDuration(timeSignature.getDenominator());
}
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 writeDivisionType(TGDivisionType divisionType){
//escribo los enters
writeByte(divisionType.getEnters());
//escribo los tiempos
writeByte(divisionType.getTimes());
}
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 writeBendEffect(TGEffectBend effect){
//escribo la cantidad de puntos
writeByte(effect.getPoints().size());
Iterator it = effect.getPoints().iterator();
while(it.hasNext()){
TGEffectBend.BendPoint point = (TGEffectBend.BendPoint)it.next();
//escribo la posicion
writeByte(point.getPosition());
//escribo el valor
writeByte(point.getValue());
}
}
private void writeTremoloBarEffect(TGEffectTremoloBar effect){
//escribo la cantidad de puntos
writeByte(effect.getPoints().size());
Iterator it = effect.getPoints().iterator();
while(it.hasNext()){
TGEffectTremoloBar.TremoloBarPoint point = (TGEffectTremoloBar.TremoloBarPoint)it.next();
//escribo la posicion
writeByte(point.getPosition());
//escribo el valor
writeByte( (point.getValue() + TGEffectTremoloBar.MAX_VALUE_LENGTH) );
}
}
private void writeHarmonicEffect(TGEffectHarmonic effect){
//excribo el tipo
writeByte(effect.getType());
//excribo la data
if(effect.getType() != TGEffectHarmonic.TYPE_NATURAL){
writeByte(effect.getData());
}
}
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());
}
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 writeMarker(TGMarker marker){
//escribo el titulo
writeUnsignedByteString(marker.getTitle());
//escribo el color
writeRGBColor(marker.getColor());
}
private void writeRGBColor(TGColor color){
//escribo el RGB
writeByte(color.getR());
writeByte(color.getG());
writeByte(color.getB());
}
private void writeLyrics(TGLyric lyrics){
//escribo el compas de comienzo
writeShort((short)lyrics.getFrom());
//escribo el texto
writeIntegerString(lyrics.getLyrics());
}
public void writeByte(int v){
try {
this.dataOutputStream.write(v);
} catch (IOException e) {
e.printStackTrace();
}
}
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) {
e.printStackTrace();
}
}
private void writeIntegerString(String v){
try {
this.dataOutputStream.writeInt(v.length());
this.dataOutputStream.writeChars(v);
} catch (IOException e) {
e.printStackTrace();
}
}
public void writeHeader(int v){
try {
this.dataOutputStream.write(v);
} catch (IOException e) {
e.printStackTrace();
}
}
public void writeHeader(int v,int bCount){
for(int i = bCount; i > 0; i --){
writeHeader( (v >>> ( (8 * i) - 8 ) ) & 0xFF);
}
}
public void writeShort(short v){
try {
this.dataOutputStream.writeShort(v);
} catch (IOException e) {
e.printStackTrace();
}
}
public class TGVoiceJoiner {
private TGFactory factory;
private TGMeasure measure;
public TGVoiceJoiner(TGFactory factory,TGMeasure measure){
this.factory = factory;
this.measure = measure.clone(factory, measure.getHeader());
this.measure.setTrack( measure.getTrack() );
}
public TGMeasure process(){
this.orderBeats();
this.joinBeats();
return this.measure;
}
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.countNotes() ; 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){
previousBestDuration.copy( previous.getVoice(0).getDuration() );
}else{
if(voice.isRestVoice()){
this.measure.removeBeat(beat);
finish = false;
break;
}
TGDuration duration = TGDuration.fromTime(this.factory, (beatStart - previousStart) );
duration.copy( previous.getVoice(0).getDuration() );
}
}
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(this.factory, (measureEnd - beatStart) );
duration.copy( voice.getDuration() );
}
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);
}
}
}
}