/*
* Created on 09-ene-2006
*
* TODO To change the template for this generated file go to
* Window - Preferences - Java - Code Style - Code Templates
*/
package org.herac.tuxguitar.io.gtp;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.herac.tuxguitar.io.base.TGFileFormat;
import org.herac.tuxguitar.io.base.TGFileFormatException;
import org.herac.tuxguitar.song.models.TGBeat;
import org.herac.tuxguitar.song.models.TGChannel;
import org.herac.tuxguitar.song.models.TGColor;
import org.herac.tuxguitar.song.models.TGDuration;
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.TGStroke;
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.TGVelocities;
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;
/**
* @author julian
*
* TODO To change the template for this generated type comment go to Window - Preferences - Java - Code Style - Code Templates
*/
public class GP3OutputStream extends GTPOutputStream{
private static final String GP3_FORMAT_EXTENSION = ".gp3";
private static final String GP3_VERSION = "FICHIER GUITAR PRO v3.00";
private static final int GP_BEND_SEMITONE = 25;
private static final int GP_BEND_POSITION = 60;
public GP3OutputStream(GTPSettings settings){
super(settings);
}
public TGFileFormat getFileFormat(){
return new TGFileFormat("Guitar Pro 3",("*" + GP3_FORMAT_EXTENSION));
}
public boolean isSupportedExtension(String extension) {
return (extension.toLowerCase().equals(GP3_FORMAT_EXTENSION)) ;
}
public void writeSong(TGSong song){
try {
if(song.isEmpty()){
throw new TGFileFormatException("Empty Song!!!");
}
TGMeasureHeader header = song.getMeasureHeader(0);
writeStringByte(GP3_VERSION, 30, DEFAULT_VERSION_CHARSET);
writeInfo(song);
writeBoolean( (header.getTripletFeel() == TGMeasureHeader.TRIPLET_FEEL_EIGHTH) );
writeInt(header.getTempo().getValue());
writeInt(0);
writeChannels(song);
writeInt(song.countMeasureHeaders());
writeInt(song.countTracks());
writeMeasureHeaders(song);
writeTracks(song);
writeMeasures(song,header.getTempo().clone(getFactory()));
close();
} catch (Exception e) {
e.printStackTrace();
}
}
private void writeInfo(TGSong song) throws IOException{
List comments = toCommentLines(song.getComments());
writeStringByteSizeOfInteger(song.getName());
writeStringByteSizeOfInteger("");
writeStringByteSizeOfInteger(song.getArtist());
writeStringByteSizeOfInteger(song.getAlbum());
writeStringByteSizeOfInteger(song.getAuthor());
writeStringByteSizeOfInteger(song.getCopyright());
writeStringByteSizeOfInteger(song.getWriter());
writeStringByteSizeOfInteger("");
writeInt( comments.size() );
for (int i = 0; i < comments.size(); i++) {
writeStringByteSizeOfInteger( (String)comments.get(i) );
}
}
private void writeChannels(TGSong song) throws IOException{
TGChannel[] channels = makeChannels(song);
for (int i = 0; i < channels.length; i++) {
writeInt(channels[i].getInstrument());
writeByte(toChannelByte(channels[i].getVolume()));
writeByte(toChannelByte(channels[i].getBalance()));
writeByte(toChannelByte(channels[i].getChorus()));
writeByte(toChannelByte(channels[i].getReverb()));
writeByte(toChannelByte(channels[i].getPhaser()));
writeByte(toChannelByte(channels[i].getTremolo()));
writeBytes(new byte[]{0,0});
}
}
private void writeMeasureHeaders(TGSong song) throws IOException {
TGTimeSignature timeSignature = getFactory().newTimeSignature();
if (song.countMeasureHeaders() > 0) {
for (int i = 0; i < song.countMeasureHeaders(); i++) {
TGMeasureHeader measure = song.getMeasureHeader(i);
writeMeasureHeader(measure, timeSignature);
timeSignature.setNumerator(measure.getTimeSignature().getNumerator());
timeSignature.getDenominator().setValue(measure.getTimeSignature().getDenominator().getValue());
}
}
}
private void writeMeasures(TGSong song,TGTempo tempo) throws IOException{
for (int i = 0; i < song.countMeasureHeaders(); i++) {
TGMeasureHeader header = song.getMeasureHeader(i);
for (int j = 0; j < song.countTracks(); j++) {
TGTrack track = song.getTrack(j);
TGMeasure measure = track.getMeasure(i);
writeMeasure(measure, (header.getTempo().getValue() != tempo.getValue()) );
}
header.getTempo().copy( tempo );
}
}
private void writeMeasureHeader(TGMeasureHeader measure, TGTimeSignature timeSignature) throws IOException {
int flags = 0;
if (measure.getNumber() == 1 || measure.getTimeSignature().getNumerator() != timeSignature.getNumerator()) {
flags |= 0x01;
}
if (measure.getNumber() == 1 || measure.getTimeSignature().getDenominator().getValue() != timeSignature.getDenominator().getValue()) {
flags |= 0x02;
}
if (measure.isRepeatOpen()) {
flags |= 0x04;
}
if (measure.getRepeatClose() > 0) {
flags |= 0x08;
}
if (measure.hasMarker()) {
flags |= 0x20;
}
writeUnsignedByte(flags);
if ((flags & 0x01) != 0) {
writeByte((byte) measure.getTimeSignature().getNumerator());
}
if ((flags & 0x02) != 0) {
writeByte((byte) measure.getTimeSignature().getDenominator().getValue());
}
if ((flags & 0x08) != 0) {
writeByte((byte) measure.getRepeatClose());
}
if ((flags & 0x20) != 0) {
writeMarker(measure.getMarker());
}
}
private void writeTracks(TGSong song) throws IOException {
for (int i = 0; i < song.countTracks(); i++) {
TGTrack track = song.getTrack(i);
createTrack(track);
}
}
private void createTrack(TGTrack track) throws IOException {
int flags = 0;
if (track.isPercussionTrack()) {
flags |= 0x01;
}
writeUnsignedByte(flags);
writeStringByte(track.getName(), 40);
writeInt(track.getStrings().size());
for (int i = 0; i < 7; i++) {
int value = 0;
if (track.getStrings().size() > i) {
TGString string = (TGString) track.getStrings().get(i);
value = string.getValue();
}
writeInt(value);
}
writeInt(1);
writeInt(track.getChannel().getChannel() + 1);
writeInt(track.getChannel().getEffectChannel() + 1);
writeInt(24);
writeInt(Math.min(Math.max(track.getOffset(),0),12));
writeColor(track.getColor());
}
private void writeMeasure(TGMeasure srcMeasure, boolean changeTempo) throws IOException {
TGMeasure measure = new GTPVoiceJoiner(getFactory(),srcMeasure).process();
int beatCount = measure.countBeats();
writeInt(beatCount);
for (int i = 0; i < beatCount; i++) {
TGBeat beat = measure.getBeat( i );
writeBeat(beat, measure, ( changeTempo && i == 0 ) );
}
}
private void writeBeat(TGBeat beat,TGMeasure measure, boolean changeTempo) throws IOException {
TGVoice voice = beat.getVoice(0);
TGDuration duration = voice.getDuration();
int flags = 0;
if (duration.isDotted() || duration.isDoubleDotted() ) {
flags |= 0x01;
}
if (!duration.getDivision().isEqual(TGDivisionType.NORMAL)) {
flags |= 0x20;
}
if(beat.isTextBeat()){
flags |= 0x04;
}
if (changeTempo) {
flags |= 0x10;
}
TGNoteEffect effect = null;
if (voice.isRestVoice()) {
flags |= 0x40;
} else if(voice.countNotes() > 0){
TGNote note = voice.getNote(0);
effect = note.getEffect();
if (effect.isVibrato() ||
effect.isTremoloBar() ||
effect.isTapping() ||
effect.isSlapping() ||
effect.isPopping() ||
effect.isHarmonic() ||
effect.isFadeIn() ||
beat.getStroke().getDirection() != TGStroke.STROKE_NONE) {
flags |= 0x08;
}
}
writeUnsignedByte(flags);
if ((flags & 0x40) != 0) {
writeUnsignedByte(2);
}
writeByte(parseDuration(duration));
if ((flags & 0x20) != 0) {
writeInt(duration.getDivision().getEnters());
}
if ((flags & 0x04) != 0) {
writeText(beat.getText());
}
if ((flags & 0x08) != 0) {
writeBeatEffects(beat,effect);
}
if ((flags & 0x10) != 0) {
writeMixChange(measure.getTempo());
}
int stringFlags = 0;
if (!voice.isRestVoice()) {
for (int i = 0; i < voice.countNotes(); i++) {
TGNote playedNote = voice.getNote(i);
int string = (7 - playedNote.getString());
stringFlags |= (1 << string);
}
}
writeUnsignedByte(stringFlags);
for (int i = 6; i >= 0; i--) {
if ((stringFlags & (1 << i)) != 0 ) {
for( int n = 0; n < voice.countNotes(); n ++){
TGNote playedNote = voice.getNote( n );
if( playedNote.getString() == (6 - i + 1) ){
writeNote(playedNote);
break;
}
}
}
}
}
private void writeNote(TGNote note) throws IOException {
int flags = ( 0x20 | 0x10 );
if(note.getEffect().isGhostNote()){
flags |= 0x04;
}
if (note.getEffect().isBend() || note.getEffect().isGrace() || note.getEffect().isSlide() || note.getEffect().isHammer()) {
flags |= 0x08;
}
writeUnsignedByte(flags);
if ((flags & 0x20) != 0) {
int typeHeader = 0x01;
if (note.isTiedNote()) {
typeHeader = 0x02;
}else if(note.getEffect().isDeadNote()){
typeHeader = 0x03;
}
writeUnsignedByte(typeHeader);
}
if ((flags & 0x10) != 0) {
writeByte((byte)(((note.getVelocity() - TGVelocities.MIN_VELOCITY) / TGVelocities.VELOCITY_INCREMENT) + 1));
}
if ((flags & 0x20) != 0) {
writeByte((byte) note.getValue());
}
if ((flags & 0x08) != 0) {
writeNoteEffects(note.getEffect());
}
}
private byte parseDuration(TGDuration duration) {
byte value = 0;
switch (duration.getValue()) {
case TGDuration.WHOLE:
value = -2;
break;
case TGDuration.HALF:
value = -1;
break;
case TGDuration.QUARTER:
value = 0;
break;
case TGDuration.EIGHTH:
value = 1;
break;
case TGDuration.SIXTEENTH:
value = 2;
break;
case TGDuration.THIRTY_SECOND:
value = 3;
break;
case TGDuration.SIXTY_FOURTH:
value = 4;
break;
}
return value;
}
private void writeText(TGText text) throws IOException {
writeStringByteSizeOfInteger(text.getValue());
}
private void writeBeatEffects(TGBeat beat, TGNoteEffect noteEffect) throws IOException {
int flags = 0;
if (noteEffect.isVibrato()) {
flags += 0x01;
}
if (noteEffect.isTremoloBar() || noteEffect.isTapping() || noteEffect.isSlapping() || noteEffect.isPopping()) {
flags += 0x20;
}
if(beat.getStroke().getDirection() != TGStroke.STROKE_NONE){
flags |= 0x40;
}
if (noteEffect.isHarmonic() && noteEffect.getHarmonic().getType() == TGEffectHarmonic.TYPE_NATURAL) {
flags += 0x04;
}
if (noteEffect.isHarmonic() && noteEffect.getHarmonic().getType() != TGEffectHarmonic.TYPE_NATURAL) {
flags += 0x08;
}
if (noteEffect.isFadeIn()) {
flags += 0x10;
}
writeUnsignedByte(flags);
if ((flags & 0x20) != 0) {
if (noteEffect.isTremoloBar()){
writeUnsignedByte(0);
writeInt(100);
}
else if (noteEffect.isTapping()){
writeUnsignedByte(1);
writeInt(0);
}
else if (noteEffect.isSlapping()){
writeUnsignedByte(2);
writeInt(0);
}
else if (noteEffect.isPopping()){
writeUnsignedByte(3);
writeInt(0);
}
}
if ((flags & 0x40) != 0) {
writeUnsignedByte( (beat.getStroke().getDirection() == TGStroke.STROKE_DOWN ? toStrokeValue(beat.getStroke()) : 0 ) );
writeUnsignedByte( (beat.getStroke().getDirection() == TGStroke.STROKE_UP ? toStrokeValue(beat.getStroke()) : 0 ) );
}
}
private void writeNoteEffects(TGNoteEffect effect) throws IOException {
int flags = 0;
if (effect.isBend()) {
flags |= 0x01;
}
if (effect.isHammer()) {
flags |= 0x02;
}
if (effect.isSlide()) {
flags |= 0x04;
}
if (effect.isGrace()) {
flags |= 0x10;
}
writeUnsignedByte(flags);
if ((flags & 0x01) != 0) {
writeBend(effect.getBend());
}
if ((flags & 0x10) != 0) {
writeGrace(effect.getGrace());
}
}
private void writeBend(TGEffectBend bend) throws IOException {
int points = bend.getPoints().size();
writeByte((byte) 1);
writeInt(0);
writeInt(points);
for (int i = 0; i < points; i++) {
TGEffectBend.BendPoint point = (TGEffectBend.BendPoint) bend.getPoints().get(i);
writeInt( (point.getPosition() * GP_BEND_POSITION / TGEffectBend.MAX_POSITION_LENGTH) );
writeInt( (point.getValue() * GP_BEND_SEMITONE / TGEffectBend.SEMITONE_LENGTH) );
writeByte((byte) 0);
}
}
private void writeGrace(TGEffectGrace grace) throws IOException {
if(grace.isDead()){
writeUnsignedByte(0xff);
}else{
writeUnsignedByte(grace.getFret());
}
writeUnsignedByte(((grace.getDynamic() - TGVelocities.MIN_VELOCITY) / TGVelocities.VELOCITY_INCREMENT) + 1);
if(grace.getTransition() == TGEffectGrace.TRANSITION_NONE){
writeUnsignedByte(0);
}
else if(grace.getTransition() == TGEffectGrace.TRANSITION_SLIDE){
writeUnsignedByte(1);
}
else if(grace.getTransition() == TGEffectGrace.TRANSITION_BEND){
writeUnsignedByte(2);
}
else if(grace.getTransition() == TGEffectGrace.TRANSITION_HAMMER){
writeUnsignedByte(3);
}
writeUnsignedByte(grace.getDuration());
}
private void writeMixChange(TGTempo tempo) throws IOException {
for (int i = 0; i < 7; i++) {
writeByte((byte) -1);
}
writeInt(tempo.getValue());
writeByte((byte) 0);
}
private void writeMarker(TGMarker marker) throws IOException {
writeStringByteSizeOfInteger(marker.getTitle());
writeColor(marker.getColor());
}
private void writeColor(TGColor color) throws IOException {
writeUnsignedByte(color.getR());
writeUnsignedByte(color.getG());
writeUnsignedByte(color.getB());
writeByte((byte)0);
}
private TGChannel[] makeChannels(TGSong song) {
TGChannel[] channels = new TGChannel[64];
for (int i = 0; i < channels.length; i++) {
channels[i] = getFactory().newChannel();
channels[i].setChannel((short)i);
channels[i].setEffectChannel((short)i);
channels[i].setInstrument((short)24);
channels[i].setVolume((short)13);
channels[i].setBalance((short)8);
channels[i].setChorus((short)0);
channels[i].setReverb((short)0);
channels[i].setPhaser((short)0);
channels[i].setTremolo((short)0);
}
Iterator it = song.getTracks();
while (it.hasNext()) {
TGTrack track = (TGTrack) it.next();
channels[track.getChannel().getChannel()].setInstrument(track.getChannel().getInstrument());
channels[track.getChannel().getChannel()].setVolume(track.getChannel().getVolume());
channels[track.getChannel().getChannel()].setBalance(track.getChannel().getBalance());
channels[track.getChannel().getEffectChannel()].setInstrument(track.getChannel().getInstrument());
channels[track.getChannel().getEffectChannel()].setVolume(track.getChannel().getVolume());
channels[track.getChannel().getEffectChannel()].setBalance(track.getChannel().getBalance());
}
return channels;
}
private int toStrokeValue( TGStroke stroke ){
if( stroke.getValue() == TGDuration.SIXTY_FOURTH ){
return 2;
}
if( stroke.getValue() == TGDuration.THIRTY_SECOND ){
return 3;
}
if( stroke.getValue() == TGDuration.SIXTEENTH ){
return 4;
}
if( stroke.getValue() == TGDuration.EIGHTH ){
return 5;
}
if( stroke.getValue() == TGDuration.QUARTER ){
return 6;
}
return 2;
}
private byte toChannelByte(short s){
return (byte) ((s + 1) / 8);
}
private List toCommentLines( String comments ){
List lines = new ArrayList();
String line = comments;
while( line.length() > Byte.MAX_VALUE ) {
String subline = line.substring(0, Byte.MAX_VALUE);
lines.add( subline );
line = line.substring( Byte.MAX_VALUE );
}
lines.add( line );
return lines;
}
}