/*
* 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.awt.Color;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.herac.tuxguitar.io.base.TGFileFormat;
import org.herac.tuxguitar.io.base.TGFileFormatException;
import org.herac.tuxguitar.song.models.Direction;
import org.herac.tuxguitar.song.models.TGBeat;
import org.herac.tuxguitar.song.models.TGChannel;
import org.herac.tuxguitar.song.models.TGDivisionType;
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.TGVelocities;
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.Transition;
/**
* @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 int GP_BEND_POSITION = 60;
private static final int GP_BEND_SEMITONE = 25;
private static final String GP3_FORMAT_EXTENSION = ".gp3";
private static final String GP3_VERSION = "FICHIER GUITAR PRO v3.00";
/** The Logger for this class. */
public static final transient Logger LOG = Logger
.getLogger(GP3OutputStream.class);
public GP3OutputStream(GTPSettings settings) {
super(settings);
}
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());
}
public TGFileFormat getFileFormat() {
return new TGFileFormat("Guitar Pro 3", ("*" + GP3_FORMAT_EXTENSION));
}
public boolean isSupportedExtension(String extension) {
return (extension.toLowerCase().equals(GP3_FORMAT_EXTENSION));
}
private TGChannel[] makeChannels(TGSong song) {
TGChannel[] channels = new TGChannel[64];
for (int i = 0; i < channels.length; i++) {
channels[i] = new TGChannel();
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);
}
for (final TGTrack track : song.getTracks()) {
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 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 byte toChannelByte(short s) {
return (byte) ((s + 1) / 8);
}
private List<String> toCommentLines(String comments) {
List<String> lines = new ArrayList<String>();
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;
}
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 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.getNotes().size() > 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().equals(Direction.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.getNotes().size(); 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.getNotes().size(); n++) {
TGNote playedNote = voice.getNote(n);
if (playedNote.getString() == (6 - i + 1)) {
writeNote(playedNote);
break;
}
}
}
}
}
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().equals(Direction.NONE)) {
flags |= 0x40;
}
if (noteEffect.isHarmonic()) {
if (noteEffect.getHarmonic().equals(HarmonicEffect.NATURAL)) {
flags += 0x04;
} else {
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().equals(Direction.DOWN) ? toStrokeValue(beat
.getStroke())
: 0));
writeUnsignedByte((beat.getStroke().getDirection().equals(Direction.UP) ? toStrokeValue(beat
.getStroke())
: 0));
}
}
private void writeBend(BendingEffect bend) throws IOException {
int points = bend.getPoints().size();
writeByte((byte) 1);
writeInt(0);
writeInt(points);
for (final EffectPoint point : bend.getPoints()) {
writeInt((point.getPosition() * GP_BEND_POSITION / EffectPoint.MAX_POSITION_LENGTH));
writeInt((point.getValue() * GP_BEND_SEMITONE / EffectPoint.SEMITONE_LENGTH));
writeByte((byte) 0);
}
}
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 writeColor(Color color) throws IOException {
writeUnsignedByte(color.getRed());
writeUnsignedByte(color.getGreen());
writeUnsignedByte(color.getBlue());
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() == Transition.NONE) {
writeUnsignedByte(0);
} else if (grace.getTransition() == Transition.SLIDE) {
writeUnsignedByte(1);
} else if (grace.getTransition() == Transition.BEND) {
writeUnsignedByte(2);
} else if (grace.getTransition() == Transition.HAMMER) {
writeUnsignedByte(3);
}
writeUnsignedByte(grace.getDuration());
}
private void writeInfo(TGSong song) throws IOException {
List<String> 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 writeMarker(TGMarker marker) throws IOException {
writeStringByteSizeOfInteger(marker.getTitle());
writeColor(marker.getColor());
}
private void writeMeasure(TGMeasure srcMeasure, boolean changeTempo)
throws IOException {
TGMeasure measure = new GTPVoiceJoiner(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 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 writeMeasureHeaders(TGSong song) throws IOException {
TGTimeSignature timeSignature = new TGTimeSignature();
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()));
}
tempo = header.getTempo().clone();
}
}
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 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()
|| note.getEffect().isLetRing()) {
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 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.isLetRing()) {
flags |= 0x08;
}
if (effect.isGrace()) {
flags |= 0x10;
}
writeUnsignedByte(flags);
if ((flags & 0x01) != 0) {
writeBend(effect.getBend());
}
if ((flags & 0x10) != 0) {
writeGrace(effect.getGrace());
}
}
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());
close();
} catch (Exception e) {
LOG.error(e);
}
}
private void writeText(TGText text) throws IOException {
writeStringByteSizeOfInteger(text.getValue());
}
private void writeTracks(TGSong song) throws IOException {
for (int i = 0; i < song.countTracks(); i++) {
TGTrack track = song.getTrack(i);
createTrack(track);
}
}
}