Samstag, 18. Juli 2015

Gitarren Tab abspielen

In diesem Post möchte ich zeigen, wie man Gitarren Tabs auslesen kann und so quasi automatisiert Lieder mit C# und dem MIDI Format spielen kann. Sogenannte Tabs sind bei Gitarrenspielern eine beliebte und einfache Variante, Lieder zu notieren. Im Textformat werden einfach die 6 Gitarrenseiten durch ---- dargestellt, an Stellern, an welchen die Seite angeschlagen wird wird eine Zahl geschrieben, welche angibt, in welchem Bund die Seite gegriffen werden muss. So lässt sich ein Lied schon ziemlich gut angeben, die Töne können auf jeden Fall alle beschrieben werden, lediglich die Tonlänge kann nicht dargestellt werden, aber oft geben Striche zwischen den Zahlen Pausen an.
Da es in diesem Format quasi alle Lieder (frei zugänglich) gibt habe ich ein kleines Programm geschrieben, welches die Tabs einliest und das interpretierte Lied dann mit den Techniken aus den vorigen Posts spielt.
Zuerst wird die Funktion ReadTab() mit einer URL aufgerufen, wessen Quellcode dann mit einem Webclient heruntergeladen wird. Enthalten 6 aufeinanderfolgende Zeilen mindestens 3 "-", interpretieren wir die Zeilen als Tab und speichern sie. Anschließend schreiben wir sie in eine Datei (pro Zeile eine Gitarrenseite).
Dann wird die Funktion ReadSong() aufgerufen. In dieser wird die Datei gelesen und die 6 Spuren gleichzeitig gelesen. Für jede Zeiteinheit wird entweder eine Pause eingefügt, wenn auf keiner der Seiten eine Note gespielt wird, oder eine Liste mit allen angeschlagenen Noten. Dabei berechnen wir die Nummer der Klaviertaste, welche dem Ton entspricht. Dies tun wir, in dem wir ausgehend von der Aufzählung Tone, welche die zu den 6 Seiten entsprechenden Tasten speichert, die Zahl aus dem Tab addieren, denn dies ist genau die Anzahl der Halbtöne (Tasten), um welche der Ursprungston erhöht wird.
Anschließend übergeben wir diese Liste von Listen von Tastennummern an die Funktion Play(). Diese geht alle Zeitpunkte durch und spielt entweder die Liste von Tönen gleichzeitig oder eine Pause ab, wie im vorigen Post beschrieben.

Der 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];
        }
    }
}

Als Beispiel habe ich hier das Lied Master of Puppets von Metallica aufgenommen. Wie gesagt, natürlich stimmt der Rythmus oft nicht und die Melodie wirkt manchmal leicht daneben, aber ich finde es doch faszinierend wie gut und genau zum Beispiel das Einleitungsriff getroffen wird und wie leicht wir solche Musikausgaben erzeugen können.
Außerdem gibt es hier noch Yesterday und Wonderwall.

Keine Kommentare:

Kommentar veröffentlichen