package org.herac.tuxguitar.io.gtp;
import java.awt.Color;
import java.io.IOException;
import org.herac.tuxguitar.gui.editors.tab.TGBeatImpl;
import org.herac.tuxguitar.gui.editors.tab.TGChordImpl;
import org.herac.tuxguitar.gui.editors.tab.TGMeasureHeaderImpl;
import org.herac.tuxguitar.gui.editors.tab.TGMeasureImpl;
import org.herac.tuxguitar.gui.editors.tab.TGNoteImpl;
import org.herac.tuxguitar.gui.editors.tab.TGTextImpl;
import org.herac.tuxguitar.gui.editors.tab.TGTrackImpl;
import org.herac.tuxguitar.io.base.TGFileFormat;
import org.herac.tuxguitar.song.managers.TGSongManager;
import org.herac.tuxguitar.song.models.Clef;
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.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.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;
/**
* @author julian
*
* TODO To change the template for this generated type comment go to
* Window - Preferences - Java - Code Style - Code Templates
*/
public class GP2InputStream extends GTPInputStream {
private static final String SUPPORTED_VERSIONS[] = new String[] {
"FICHIER GUITAR PRO v2.20", "FICHIER GUITAR PRO v2.21" };
private static final short TRACK_CHANNELS[][] = new short[][] {
new short[] { 0, 1 }, new short[] { 2, 3 }, new short[] { 4, 5 },
new short[] { 6, 7 }, new short[] { 8, 10 }, new short[] { 11, 12 },
new short[] { 13, 14 }, new short[] { 9, 9 }, };
private static final int TRACK_COUNT = 8;
public GP2InputStream(GTPSettings settings) {
super(settings, SUPPORTED_VERSIONS);
}
private TGBeat getBeat(TGMeasure measure, long start) {
if (start >= measure.getStart()
&& start < (measure.getStart() + measure.getLength())) {
for (final TGBeat beat : measure.getBeats()) {
if (beat.getStart() == start) {
return beat;
}
}
}
return null;
}
private TGBeat getBeat(TGTrack track, TGMeasure measure, long start) {
TGBeat beat = getBeat(measure, start);
if (beat == null) {
for (int i = (track.countMeasures() - 1); i >= 0; i--) {
beat = getBeat(track.getMeasure(i), start);
if (beat != null) {
break;
}
}
}
return beat;
}
private Clef getClef(TGTrack track) {
if (!track.isPercussionTrack()) {
for (final TGString string : track.getStrings()) {
if (string.getValue() <= 34) {
return Clef.BASS;
}
}
}
return Clef.TREBLE;
}
public TGFileFormat getFileFormat() {
return new TGFileFormat("Guitar Pro 2", "*.gtp");
}
private int parseRepeatAlternative(TGSong song, int measure, int value) {
int repeatAlternative = 0;
int existentAlternatives = 0;
for (final TGMeasureHeader header : song.getMeasureHeaders()) {
if (header.getNumber() == measure) {
break;
}
if (header.isRepeatOpen()) {
existentAlternatives = 0;
}
existentAlternatives |= header.getRepeatAlternative();
}
for (int i = 0; i < 8; i++) {
if (value > i && (existentAlternatives & (1 << i)) == 0) {
repeatAlternative |= (1 << i);
}
}
return repeatAlternative;
}
private long readBeat(TGTrack track, TGMeasure measure, long start,
long lastReadedStart) throws IOException {
readInt();
TGBeat beat = new TGBeatImpl();
TGVoice voice = beat.getVoice(0);
TGDuration duration = readDuration();
TGNoteEffect effect = new TGNoteEffect();
int flags1 = readUnsignedByte();
int flags2 = readUnsignedByte();
if ((flags2 & 0x02) != 0) {
readMixChange(measure.getTempo());
}
if ((flags2 & 0x01) != 0) {
readUnsignedByte(); // strokeType
readUnsignedByte(); // strokeDuration
}
duration.setDotted(((flags1 & 0x10) != 0));
if ((flags1 & 0x20) != 0) {
duration.setDivision(TGDivisionType.DEFAULT);
skip(1);
}
// beat effects
if ((flags1 & 0x04) != 0) {
readBeatEffects(effect);
}
// chord diagram
if ((flags1 & 0x02) != 0) {
readChord(track.stringCount(), beat);
}
// text
if ((flags1 & 0x01) != 0) {
readText(beat);
}
if ((flags1 & 0x40) != 0) {
if (lastReadedStart < start) {
TGBeat previousBeat = getBeat(track, measure, lastReadedStart);
if (previousBeat != null) {
TGVoice previousVoice = previousBeat.getVoice(0);
for (final TGNote previous : previousVoice.getNotes()) {
TGNote note = new TGNoteImpl();
note.setValue(previous.getValue());
note.setString(previous.getString());
note.setVelocity(previous.getVelocity());
note.setTiedNote(true);
voice.addNote(note);
}
}
}
} else if ((flags1 & 0x08) == 0) {
int stringsFlags = readUnsignedByte();
int effectsFlags = readUnsignedByte();
int graceFlags = readUnsignedByte();
for (int i = 5; i >= 0; i--) {
if ((stringsFlags & (1 << i)) != 0) {
TGNote note = new TGNoteImpl();
int fret = readUnsignedByte();
int dynamic = readUnsignedByte();
if ((effectsFlags & (1 << i)) != 0) {
readNoteEffects(effect);
}
note.setValue((fret >= 0 && fret < 100) ? fret : 0);
note
.setVelocity((TGVelocities.MIN_VELOCITY + (TGVelocities.VELOCITY_INCREMENT * dynamic))
- TGVelocities.VELOCITY_INCREMENT);
note.setString(track.stringCount() - i);
note.setEffect(effect.clone());
note.getEffect().setDeadNote((fret < 0 || fret >= 100));
voice.addNote(note);
}
// Grace note
if ((graceFlags & (1 << i)) != 0) {
readGraceNote();
}
}
}
beat.setStart(start);
voice.setEmpty(false);
voice.setDuration(duration.clone());
measure.addBeat(beat);
return duration.getTime();
}
private void readBeatEffects(TGNoteEffect effect) throws IOException {
int flags = readUnsignedByte();
effect.setVibrato((flags == 1 || flags == 2));
effect.setFadeIn((flags == 4));
effect.setTapping((flags == 5));
effect.setSlapping((flags == 6));
effect.setPopping((flags == 7));
if (flags == 3) {
readBend(effect);
} else if (flags == 8 || flags == 9) {
HarmonicEffect harmonic = null;
if (flags == 8) {
harmonic = HarmonicEffect.NATURAL;
} else {
harmonic = HarmonicEffect.ARTIFICIAL;
harmonic.setData(0);
}
effect.setHarmonic(harmonic);
}
}
private void readBend(TGNoteEffect effect) throws IOException {
skip(6);
float value = Math.max(((readUnsignedByte() / 8f) - 26f), 1f);
BendingEffect bend = new BendingEffect();
bend.addPoint(0, 0);
bend.addPoint(Math.round(EffectPoint.MAX_POSITION_LENGTH / 2), Math
.round(value * EffectPoint.SEMITONE_LENGTH));
bend.addPoint(Math.round(EffectPoint.MAX_POSITION_LENGTH), Math.round(value
* EffectPoint.SEMITONE_LENGTH));
effect.setBend(bend);
skip(1);
}
private void readChord(int strings, TGBeat beat) throws IOException {
TGChord chord = new TGChordImpl(strings);
chord.setName(readStringByte(0));
this.skip(1);
if (readInt() < 12) {
skip(32);
}
chord.setFirstFret(readInt());
if (chord.getFirstFret() != 0) {
for (int i = 0; i < 6; i++) {
int fret = readInt();
if (i < chord.countStrings()) {
chord.addFretValue(i, fret);
}
}
}
if (chord.countNotes() > 0) {
beat.setChord(chord);
}
}
private TGDuration readDuration() throws IOException {
TGDuration duration = new TGDuration();
duration.setValue((int) (Math.pow(2, (readByte() + 4)) / 4));
return duration;
}
private void readGraceNote() throws IOException {
byte bytes[] = new byte[3];
read(bytes);
}
private void readInfo(TGSong song) throws IOException {
song.setName(readStringByteSizeOfByte());
song.setAuthor(readStringByteSizeOfByte());
readStringByteSizeOfByte();
}
private void readMixChange(TGTempo tempo) throws IOException {
int flags = readUnsignedByte();
// Tempo
if ((flags & 0x20) != 0) {
tempo.setValue(readInt());
readUnsignedByte();
}
// Reverb
if ((flags & 0x10) != 0) {
readUnsignedByte();
readUnsignedByte();
}
// Chorus
if ((flags & 0x08) != 0) {
readUnsignedByte();
readUnsignedByte();
}
// Balance
if ((flags & 0x04) != 0) {
readUnsignedByte();
readUnsignedByte();
}
// Volume
if ((flags & 0x02) != 0) {
readUnsignedByte();
readUnsignedByte();
}
// Instrument
if ((flags & 0x01) != 0) {
readUnsignedByte();
}
}
private void readNoteEffects(TGNoteEffect effect) throws IOException {
int flags = readUnsignedByte();
effect.setHammer((flags == 1 || flags == 2));
effect.setSlide((flags == 3 || flags == 4));
if (flags == 5 || flags == 6) {
readBend(effect);
}
}
public TGSong readSong() throws GTPFormatException, IOException {
readVersion();
if (!isSupportedVersion(getVersion())) {
this.close();
throw new GTPFormatException("Unsupported Version");
}
TGSong song = new TGSong();
readInfo(song);
int tempo = readInt();
int tripletFeel = ((readInt() == 1) ? TGMeasureHeader.TRIPLET_FEEL_EIGHTH
: TGMeasureHeader.TRIPLET_FEEL_NONE);
readInt(); // key
for (int i = 0; i < TRACK_COUNT; i++) {
TGTrack track = new TGTrackImpl();
track.setNumber((i + 1));
track.getChannel().setChannel(TRACK_CHANNELS[i][0]);
track.getChannel().setEffectChannel(TRACK_CHANNELS[i][1]);
track.setColor(Color.RED);
int strings = readInt();
for (int j = 0; j < strings; j++) {
track.getStrings().add(new TGString(j+1, readInt()));
}
song.addTrack(track);
}
int measureCount = readInt();
for (int i = 0; i < TRACK_COUNT; i++) {
readTrack(song.getTrack(i));
}
skip(10);
TGMeasureHeader previous = null;
long[] lastReadedStarts = new long[TRACK_COUNT];
for (int i = 0; i < measureCount; i++) {
TGMeasureHeader header = new TGMeasureHeaderImpl();
header.setStart((previous == null) ? TGDuration.QUARTER_TIME : (previous
.getStart() + previous.getLength()));
header.setNumber((previous == null) ? 1 : previous.getNumber() + 1);
header.getTempo().setValue(
(previous == null) ? tempo : previous.getTempo().getValue());
header.setTripletFeel(tripletFeel);
readTrackMeasures(song, header, lastReadedStarts);
previous = header;
}
TGSongManager manager = new TGSongManager();
manager.setSong(song);
manager.autoCompleteSilences();
this.close();
return song;
}
private void readText(TGBeat beat) throws IOException {
TGText text = new TGTextImpl();
text.setValue(readStringByte(0));
beat.setText(text);
}
private void readTimeSignature(TGTimeSignature timeSignature)
throws IOException {
timeSignature.setNumerator(readUnsignedByte());
timeSignature.getDenominator().setValue(readUnsignedByte());
}
private void readTrack(TGTrack track) throws IOException {
track.getChannel().setInstrument((short) readInt());
readInt(); // Number of frets
track.setName(readStringByteSizeOfByte());
track.setSolo(readBoolean());
track.getChannel().setVolume((short) readInt());
track.getChannel().setBalance((short) readInt());
track.getChannel().setChorus((short) readInt());
track.getChannel().setReverb((short) readInt());
track.setOffset(readInt());
}
private void readTrackMeasures(TGSong song, TGMeasureHeader header,
long[] lastReadedStarts) throws IOException {
readTimeSignature(header.getTimeSignature());
skip(6);
int[] beats = new int[TRACK_COUNT];
for (int i = 0; i < TRACK_COUNT; i++) {
readUnsignedByte();
readUnsignedByte();
beats[i] = readUnsignedByte();
if (beats[i] > 127) {
beats[i] = 0;
}
skip(9);
}
skip(2);
int flags = readUnsignedByte();
header.setRepeatOpen(((flags & 0x01) != 0));
if ((flags & 0x02) != 0) {
header.setRepeatClose(readUnsignedByte());
}
if ((flags & 0x04) != 0) {
header.setRepeatAlternative(parseRepeatAlternative(song, header
.getNumber(), readUnsignedByte()));
}
song.addMeasureHeader(header);
for (int i = 0; i < TRACK_COUNT; i++) {
TGTrack track = song.getTrack(i);
TGMeasure measure = new TGMeasureImpl(header);
long start = measure.getStart();
for (int j = 0; j < beats[i]; j++) {
long length = readBeat(track, measure, start, lastReadedStarts[i]);
lastReadedStarts[i] = start;
start += length;
}
measure.setClef(getClef(track));
track.addMeasure(measure);
}
}
}