Saturday, July 18, 2015

Play Guitar Tabs with C#

In this post I want to show how one can read in guitar tabs and thus can automatically play songs with C# and the MIDI format. So called tabs are a popular and easy variant to notate songs. In the textformat simply the 6 guitar strings are represented by ---, on positions, where the strings have to be played a number is displayed which describes in which fret the string should be touched. This way a song can be represented pretty well, the tones definitely all can be described, only the tone duration cannot be represented, but often "-" between the numbers denote pauses. Since nearly all songs are (freely) available in this format I wrote a little program which reads in the tabs and then plays the interpreted song with the techniques from the previous posts.
First the function ReadTab() is called with an URL, its sourcecode is then downloaded with a Webclient. If 6 successive lines contain at least 3 "-", we interpret these lines as tabs and save then, eventually to a file (one line per guitar string).
Then the function ReadSong() is called. In this the file is read, the 6 lines are read simultaneously. For every point in time a pause is added if on no string a note is played, or a list with all played notes. For this we calculate the number of the to the tone corresponding piano key. We do this by starting from the enumeration Tone, which saves the to the 6 strings belonging keys, and add to this the number of half tones (keys), by which the string tone has to be incremented.
Finally we pass this list of lists of key numbers to the function Play(). It runs through all points in time and either plays the list of tones simultaneously or a break, as described in the previous post.

The code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.IO;
using Midi;
using System.Threading;

namespace TabReader
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            ReadTab("http://tabs.ultimate-guitar.com/m/metallica/master_of_puppets_tab.htm");    
        }

        int NoteDuration = 70;

        protected enum Tone
        {
            E2 = 20,
            A = 25,
            D = 30,
            G = 35,
            B = 39,
            E4 = 44,
        }

        public void ReadTab(string url)
        {
            System.Net.WebClient wc = new System.Net.WebClient();
            string HTML = wc.DownloadString(url);
            string[] Lines = HTML.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
            string[] Tabs = new string[6];

            for (int i = 0; i < Lines.Length - 6; i++)
            {
                bool Found = true;
                for (int j = 0; j < 6; j++)
                {
                    if (Lines[i + j].Count(f => f == '-') <= 3)
                    {
                        Found = false;
                        break;
                    }
                }
                if (Found)
                {
                    for (int j = 0; j < 6; j++)
                    {
                        Tabs[j] += Lines[i + j];
                    }
                    i += 6;
                }
            }

            string FileName = url.Replace("/", "").Replace("http", "").Replace(":", "");
            StreamWriter sw = new StreamWriter(FileName);
            for (int i = 0; i < 6; i++)
            {
                sw.WriteLine(Tabs[i]);
            }
            sw.Close();

            List<List<int>> Result = ReadSong(FileName);
            Play(Result);
        }

        public List<List<int>> ReadSong(string path)
        {
            System.IO.StreamReader sr = new System.IO.StreamReader(path);
            string[] Strings = new string[6];
            for (int i = 0; i < 6; i++)
            {
                Strings[i] = "";
            }

            string Temp = "";
            while ((Temp = sr.ReadLine()) != null)
            {
                Strings[5] += Temp;
                for (int i = 4; i >= 0; i--)
                {
                    Strings[i] += sr.ReadLine();
                }
            }
            sr.Close();

            List<List<int>> Song = new List<List<int>>();

            for (int i = 0; i < Strings[0].Length; i++)
            {

                bool ToneFound = false;

                List<int> Current = new List<int>();

                for (int j = 0; j < 6; j++)
                {
                    if (Strings[j][i] != '-' && Strings[j][i] != '/' && Strings[j][i] != '|')
                    {
                        int Dummy;

                        if (int.TryParse(Strings[j][i].ToString(), out Dummy))
                        {
                            Current.Add(((int)((Tone[])(Enum.GetValues(typeof(Tone))))[j] + int.Parse(Strings[j][i].ToString())));
                            ToneFound = true;
                        }
                    }

                }

                if (!ToneFound && Strings[0][i] == '-')
                    Song.Add(null);
                else
                    Song.Add(Current);
            }

            Play(Song);
            return Song;
        }

        public void Play(List<List<int>> keys)
        {
            OutputDevice outputDevice = OutputDevice.InstalledDevices[0];
            outputDevice.Open();

            foreach (List<int> t in keys)
            {
                if (t == null)
                    Thread.Sleep(NoteDuration);
                else
                {
                    foreach (int s in t)
                    {
                        outputDevice.SendNoteOn(Channel.Channel1, GetFreq(s), 80);
                    }
                    Thread.Sleep(NoteDuration);
                }
            }
        }

        public Pitch GetFreq(int key)
        {
            return ((Pitch[])Enum.GetValues(typeof(Pitch)))[key + 21];
        }
    }
}

As an example I here recorded the song Master of Puppets by Metallica. As already mentioned, of course the rhytm often is not correct and the melody sometimes seems off. But still I find it fascinating how good for example the intro riff is hit and how easy we can create such music outputs.
Also I recorded Yesterday and Wonderwall.

No comments:

Post a Comment