diff --git a/RocksmithToTab/CmdOptions.cs b/RocksmithToTab/CmdOptions.cs
index c1e30c1..b54cfe5 100644
--- a/RocksmithToTab/CmdOptions.cs
+++ b/RocksmithToTab/CmdOptions.cs
@@ -29,7 +29,7 @@ class CmdOptions
[Option('o', "outdir", DefaultValue = "rocksmith_tabs", HelpText = "Path to the directory where tabs should be created.")]
public string OutputDirectory { get; set; }
- [Option('f', "format", DefaultValue = "gp5", HelpText = "File output format, currently either 'gp5', 'gpx' or 'gpif'.")]
+ [Option('f', "format", DefaultValue = "gp5", HelpText = "File output format, currently either 'gp5', 'gpx', 'gpif', or 'txt'")]
public string OutputFormat { get; set; }
[Option('n', "name", DefaultValue = "{artist} - {title}", HelpText = "Format of the output file names. For a list of available field names, refer to the readme.")]
diff --git a/RocksmithToTab/Program.cs b/RocksmithToTab/Program.cs
index 8cd55a4..5a0711c 100644
--- a/RocksmithToTab/Program.cs
+++ b/RocksmithToTab/Program.cs
@@ -332,6 +332,7 @@ static void ExportArrangement(Score score, Song2014 arrangement, string identifi
static GpxExporter gpxExporter = new GpxExporter();
static GP5File gp5Exporter = new GP5File();
+ static TxtExporter txtExporter = new TxtExporter();
static void SaveScore(Score score, string baseFileName, string outputDirectory, string outputFormat)
{
@@ -348,6 +349,10 @@ static void SaveScore(Score score, string baseFileName, string outputDirectory,
{
gpxExporter.ExportGpif(score, basePath + ".gpif");
}
+ else if (outputFormat == "txt")
+ {
+ txtExporter.ExportTxt(score, basePath + ".txt");
+ }
else
{
gpxExporter.ExportGPX(score, basePath + ".gpx");
diff --git a/RocksmithToTabGUI/MainWindow.Designer.cs b/RocksmithToTabGUI/MainWindow.Designer.cs
index 82e0a9d..cedf0fc 100644
--- a/RocksmithToTabGUI/MainWindow.Designer.cs
+++ b/RocksmithToTabGUI/MainWindow.Designer.cs
@@ -164,7 +164,8 @@ private void InitializeComponent()
this.OutputFormat.Items.AddRange(new object[] {
".gp5 (Guitar Pro 5 / TuxGuitar)",
".gpx (Guitar Pro 6)",
- ".gpif (Guitar Pro 6)"});
+ ".gpif (Guitar Pro 6)",
+ ".txt"});
this.OutputFormat.Location = new System.Drawing.Point(5, 156);
this.OutputFormat.Name = "OutputFormat";
this.OutputFormat.Size = new System.Drawing.Size(493, 21);
diff --git a/RocksmithToTabGUI/MainWindow.cs b/RocksmithToTabGUI/MainWindow.cs
index 33f97a9..c6898bd 100644
--- a/RocksmithToTabGUI/MainWindow.cs
+++ b/RocksmithToTabGUI/MainWindow.cs
@@ -93,7 +93,7 @@ private void CreateTabs_Click(object sender, EventArgs e)
return;
}
- string[] fileFormats = new string[] { "gp5", "gpx", "gpif" };
+ string[] fileFormats = new string[] { "gp5", "gpx", "gpif", "txt" };
using (var callProgram = new CallProgram())
{
callProgram.RocksmithPath = RocksmithFolder.Text;
diff --git a/RocksmithToTabLib/RocksmithToTabLib.csproj b/RocksmithToTabLib/RocksmithToTabLib.csproj
index aca29d7..c90b7b3 100644
--- a/RocksmithToTabLib/RocksmithToTabLib.csproj
+++ b/RocksmithToTabLib/RocksmithToTabLib.csproj
@@ -85,6 +85,7 @@
+
diff --git a/RocksmithToTabLib/TxtExporter.cs b/RocksmithToTabLib/TxtExporter.cs
new file mode 100644
index 0000000..e029530
--- /dev/null
+++ b/RocksmithToTabLib/TxtExporter.cs
@@ -0,0 +1,376 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace RocksmithToTabLib
+{
+ public class TxtExporter
+ {
+ const int MaxLine = 120;
+ static readonly string[] noteLetters = { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B" };
+
+ public void ExportTxt(Score score, string fileName)
+ {
+ var dirPath = Path.GetDirectoryName(fileName);
+ var baseName = Path.GetFileNameWithoutExtension(fileName);
+ var extName = Path.GetExtension(fileName);
+
+ foreach (var track in score.Tracks)
+ {
+ var trackFileName = string.Format(@"{0}\{1} ({2}){3}",
+ dirPath, baseName, track.Name, extName);
+ File.WriteAllLines(trackFileName, this.GetLines(score, track));
+ }
+ }
+
+ private IEnumerable GetLines(Score score, Track track)
+ {
+ foreach (var line in this.GetMetadataLines(score))
+ yield return line.TrimEnd();
+
+ foreach (var line in this.GetTrackHeaderLines(track))
+ yield return line.TrimEnd();
+
+ foreach (var line in this.GetTrackChordLines(track))
+ yield return line.TrimEnd();
+
+ foreach (var line in this.GetTrackTabLines(track))
+ yield return line.TrimEnd();
+ }
+
+ private IEnumerable GetMetadataLines(Score score)
+ {
+ if (!string.IsNullOrWhiteSpace(score.Title))
+ yield return string.Format("Title: {0}", score.Title);
+
+ if (!string.IsNullOrWhiteSpace(score.Artist))
+ yield return string.Format("Artist: {0}", score.Artist);
+
+ if (!string.IsNullOrWhiteSpace(score.Album))
+ yield return string.Format("Album: {0}", score.Album);
+
+ yield return string.Empty;
+
+ foreach (var comment in score.Comments)
+ yield return comment;
+ }
+
+ private IEnumerable GetTrackChordLines(Track track)
+ {
+ var usedChords = (from bar in track.Bars
+ from chord in bar.Chords
+ where chord.ChordId != -1
+ select chord.ChordId).Distinct();
+ if (usedChords.Count() == 0)
+ yield break;
+
+ var maxNameWidth = track.ChordTemplates.Max(kvp => kvp.Value.Name.Length);
+ if (maxNameWidth == 0)
+ yield break; // don't print chords if none of them have names
+
+ var maxFret = track.ChordTemplates.SelectMany(ct => ct.Value.Frets).Max().ToString().Length;
+ var maxWidth = Math.Max(maxFret, maxNameWidth);
+
+
+ var generator = new TabGenerator(this.GetStringNotePrefix(track), ": ", " ", string.Empty);
+ foreach (var kvp in track.ChordTemplates)
+ {
+ var chordId = kvp.Key;
+ var chord = kvp.Value;
+
+ if (string.IsNullOrWhiteSpace(chord.Name))
+ continue;
+
+ if (!usedChords.Contains(chordId))
+ continue;
+
+ var nameSpacer = new string(' ', maxWidth - chord.Name.Length);
+ var name = string.Format("{0}{1}", nameSpacer, chord.Name);
+
+ var frets = chord.Frets.Reverse().ToArray();
+ var strings = new string[track.NumStrings];
+ for (var stringIndex = 0; stringIndex < strings.Length; stringIndex++)
+ {
+ var f = frets[stringIndex];
+ var s = f == -1 ? "-" : f.ToString();
+ var spacer = maxWidth - s.Length;
+ if (spacer < 0)
+ spacer = 0;
+ strings[stringIndex] = string.Format("{0}{1}", new string(' ', spacer), s);
+ }
+ generator.AddBar(name, strings);
+ }
+
+ foreach (var line in generator.GetRows(MaxLine))
+ yield return line;
+
+ yield return string.Empty;
+ }
+
+ private string[] GetStringNotePrefix(Track track)
+ {
+ var tuning = track.Tuning.ToArray();
+ if (tuning.Length > track.NumStrings)
+ tuning = tuning.Skip(tuning.Length - track.NumStrings).ToArray();
+
+ var notes = new List();
+
+ var maxWidth = 0;
+ for (var index = 0; index < tuning.Length; index++)
+ {
+ var tune = tuning[index];
+ var noteIndex = tune % 12;
+ var note = noteLetters[noteIndex];
+ if (index == tuning.Length - 1 && track.Instrument == Track.InstrumentType.Guitar)
+ note = note.ToLower();
+
+ notes.Add(note);
+ maxWidth = Math.Max(maxWidth, note.Length);
+ }
+
+ return (from note in Enumerable.Reverse(notes)
+ let spacer = maxWidth - note.Length
+ select string.Format("{0}{1}", new string(' ', spacer), note)).ToArray();
+ }
+
+ private IEnumerable GetTrackHeaderLines(Track track)
+ {
+ yield return string.Format("===== {0} =====", track.Name);
+
+ if (track.Capo > 0)
+ yield return string.Format("Capo on fret {0}", track.Capo);
+
+ yield return string.Empty;
+ }
+
+ private NoteText ToText(Note prevNote, Note thisNote, Note nextNote)
+ {
+ var linkNext = false;
+ if (thisNote == null)
+ return new NoteText { Text = "-" };
+
+ var f = thisNote.Fret.ToString();
+
+ float? last = thisNote.Fret;
+ foreach (var b in thisNote.BendValues)
+ {
+ var pos = thisNote.Fret + b.Step;
+ if (last.HasValue && last.Value == pos)
+ continue;
+
+ string s = last.HasValue && pos < last ? "r" : "b";
+ f += string.Format("{0}{1}", s, pos);
+ last = pos;
+ }
+
+ if (thisNote.PalmMuted)
+ f = string.Format("({0})", f);
+
+ if (thisNote.Tapped)
+ f += "t";
+
+ if (thisNote.Harmonic || thisNote.PinchHarmonic)
+ f = string.Format("<{0}>", f);
+
+ switch (thisNote.Slide)
+ {
+ case Note.SlideType.ToNext:
+ f += nextNote.Fret > thisNote.Fret ? "/" : "\\";
+ linkNext = true;
+ break;
+
+ case Note.SlideType.UnpitchUp:
+ f += "/";
+ break;
+
+ case Note.SlideType.UnpitchDown:
+ f += "\\";
+ break;
+ }
+
+ if (thisNote.Hopo)
+ {
+ f += nextNote == null || nextNote.Fret > thisNote.Fret ? "h" : "p";
+ linkNext = true;
+ }
+ return new NoteText { Text = f, Linked = linkNext };
+ }
+
+ class NoteText
+ {
+ public string Text { get; set; }
+ public bool Linked { get; set; }
+ }
+
+ private static T TryGet(Dictionary dict, int key)
+ {
+ if (dict == null)
+ return default(T);
+
+ T note = default(T);
+ dict.TryGetValue(key, out note);
+ return note;
+ }
+
+ private IEnumerable GetTrackTabLines(Track track)
+ {
+ var generator = new TabGenerator(GetStringNotePrefix(track), "|-", "-|-", "-|");
+
+ Chord lastChord = null;
+
+ bool linkNext = false;
+
+ for (var barIndex = 0; barIndex < track.Bars.Count; barIndex++)
+ {
+ var lines = Enumerable.Range(0, track.NumStrings).Select(x => string.Empty).ToArray();
+
+ var bar = track.Bars[barIndex];
+ var header = string.Empty;
+ var lastHeader = string.Empty;
+
+ // print out each note
+ for (int chordIndex = 0; chordIndex < bar.Chords.Count; chordIndex++)
+ {
+ var thisChord = bar.Chords[chordIndex];
+ var nextChord =
+ chordIndex + 1 < bar.Chords.Count ? bar.Chords[chordIndex + 1] :
+ barIndex + 1 < track.Bars.Count ? track.Bars[barIndex + 1].Chords[0] :
+ null;
+
+ var notes = (from stringIndex in Enumerable.Range(0, track.NumStrings).Reverse()
+ let lastNote = lastChord == null ? null : TryGet(lastChord.Notes, stringIndex)
+ let thisNote = TryGet(thisChord.Notes, stringIndex)
+ let nextNote = nextChord == null ? null : TryGet(nextChord.Notes, stringIndex)
+ select ToText(lastNote, thisNote, nextNote)).ToArray();
+ var chordHeader = thisChord.ChordId == -1 ? string.Empty : track.ChordTemplates[thisChord.ChordId].Name;
+ var width = notes.Max(t => t.Text.Length);
+ if (!linkNext)
+ width += 1;
+
+ if (chordHeader != lastHeader)
+ {
+ var lineLength = lines[0].Length;
+ if (lineLength < header.Length)
+ lines = lines.Select(l => l += new string('-', header.Length - lineLength)).ToArray();
+ else if (lineLength > header.Length)
+ header += new string(' ', lineLength - header.Length);
+
+ header += chordHeader + " ";
+ lastHeader = chordHeader;
+ }
+
+ for (int x = 0; x < lines.Length; x++)
+ {
+ var spacer = width - notes[x].Text.Length;
+ lines[x] += string.Format("{0}{1}", new string('-', spacer), notes[x].Text);
+ }
+
+ linkNext = notes.Any(t => t.Linked);
+ lastChord = thisChord;
+ }
+
+ if (header.Length < lines[0].Length)
+ header += new string(' ', lines[0].Length - header.Length);
+ else if (header.Length > lines[0].Length)
+ lines = lines.Select(l => l += new string('-', header.Length - lines[0].Length)).ToArray();
+ generator.AddBar(header, lines);
+ }
+
+ foreach (var line in generator.GetRows(MaxLine))
+ yield return line;
+ }
+ }
+
+ class TabGenerator
+ {
+ public string[] Strings { get; private set; }
+ public List Bars { get; private set; }
+ public List Headers { get; private set; }
+
+ public string Prefix { get; set; }
+ public string Connector { get; set; }
+ public string Suffix { get; set; }
+
+ public TabGenerator(string[] strings, string prefix, string connector, string suffix)
+ {
+ this.Prefix = prefix;
+ this.Connector = connector;
+ this.Suffix = suffix;
+
+ this.Strings = strings;
+ this.Bars = new List();
+ this.Headers = new List();
+ }
+
+ public void AddBar(string header, string[] strings)
+ {
+ if (header.Length != strings[0].Length)
+ throw new ArgumentOutOfRangeException(
+ "header", header.Length, "Header length does not match line length");
+
+ if (strings.Length != this.Strings.Length)
+ throw new ArgumentOutOfRangeException(
+ "strings", strings.Length,
+ "Notes should have a length of " + this.Strings.Length);
+
+ var firstLength = strings[0].Length;
+ var allSameLength = strings.Skip(1).All(n => n.Length == firstLength);
+ if (!allSameLength)
+ throw new ArgumentOutOfRangeException(
+ "strings", string.Join(" || ", strings),
+ "All strings were not the same length");
+
+ this.Headers.Add(header);
+ this.Bars.Add(strings);
+ }
+
+ public IEnumerable GetRows(int maxLength)
+ {
+ var prefixLength = this.Strings[0].Length + this.Prefix.Length; // add one for the first "|"
+ var suffixLength = this.Suffix.Length;
+ var bars = new List();
+ var headers = new List();
+
+ for (var index = 0; index < this.Bars.Count; index++)
+ {
+ var bar = this.Bars[index];
+ var header = this.Headers[index];
+
+ bars.Add(bar);
+ headers.Add(header);
+
+ var noteLength = bars.Sum(b => b[0].Length);
+ var connectorLength = (bars.Count - 1) * this.Connector.Length;
+ var totalLength = prefixLength + noteLength + suffixLength + connectorLength;
+ if (totalLength <= maxLength)
+ continue;
+
+ headers.Remove(header);
+ bars.Remove(bar);
+ var headerSpacer = new string(' ', prefixLength);
+ var headerJoin = new string(' ', this.Connector.Length);
+ yield return string.Format("{0}{1}", headerSpacer, string.Join(headerJoin, headers));
+ foreach (var line in this.MakeRow(bars))
+ yield return line;
+
+ bars.Clear();
+ headers.Clear();
+ }
+ }
+
+ private IEnumerable MakeRow(IEnumerable bars)
+ {
+ for (var stringIndex = 0; stringIndex < this.Strings.Length; stringIndex++)
+ {
+ var line = string.Join(this.Connector, bars.Select(bar => bar[stringIndex]));
+
+ yield return string.Format(
+ "{0}{2}{1}{3}",
+ this.Strings[stringIndex], line,
+ this.Prefix, this.Suffix);
+ }
+ }
+ }
+}