Mittwoch, 30. September 2015

Sudoku App für Android

Heute mal etwas Offtopic-mäßiges: Neben meiner Sudoku Seite habe ich nun eine App für Android geschrieben, mit welcher die Sudokus einfach und bequem auf dem Smartphone gelöst werden können. Ich würde mich freuen, wenn der eine oder andere sie mal ausprobiert, oder selber Interesse an der App Entwicklung findet. Die App habe ich in Java geschrieben, über die Erstellung von Android Apps mit C# gibt es auf diesem Blog eine Einführung.
Die App kann kostenlos über den Google Play Store heruntergeladen werden.

Samstag, 19. September 2015

Matlab Tutorial Teil 5 - Plots

Im heutigen Post möchte ich eine kurze Einführung in die Erstellung von Plots mit Matlab geben.
Dieser Beitrag soll nur einen groben Überblick vermitteln, für genaueres verweise ich auf die Dokumentation von Matlab. Dort sind alle Plot Arten mit Parametern aufgelistet, dort wird auch zum Beispiel erklärt wie man Achsen beschriftet, Farben ändert etc.
Hier möchte ich zwei Arten von 2-dimensionalen Plots erläutern, nämlich einfache Linien- und Balkendiagramme, sowie den Surface Plot als 3-dimensionalen.
Ein Liniendiagramm (line plot) erstellen wir mit der Funktion plot(). Dafür legen wir zuerst ein paar Testdaten mittels x = rand(8, 1) an, also einen Vektor mit 8 zufälligen Werten zwischen 0 und 1.
Dann liefert plot(x) das folgende Ergebnis:






















bar() erstellt ein Balkendiagramm (bar chart) aus den gegebenen Daten. bar(x) sieht wie folgt aus:






















Nun zu einem 3-dimensionalen Plot, dem Surface Plot. Dieser stellt (zum Beispiel) eine übergebene 2-dimensionale Matrix in der dritten Dimension (durch Höhe und Farbe) dar. Am Punkt mit Koordinaten (i, j) wird also der (i, j). - Eintrag der Matrix dargestellt, das heißt sein Wert durch die z - Koordinate dargestellt und zusätzlich passend eingefärbt.
Wir erstellen als Beispiel mittels A = rand(32) eine zufällige 32 x 32 Matrix. surf(A) erstellt dann das folgende Diagramm:



Freitag, 18. September 2015

Matlab Tutorial Teil 4 - Skripte und Funktionen

Um oft benutzten Code nicht jedes mal neu eingeben zu müssen haben wir in Matlab die Möglichkeit diesen in Skripte und Funktionen auszulagern.
Diese werden in Dateien mit der Endung .m gespeichert. Skripte sind einfach Sammlungen von Codezeilen, während Funktionen dazu noch einen Rückgabewert liefern.
Diese Dateien können wir bequem aus der Matlab Entwicklungsumgebung anlegen: Hierzu geben wir edit Name.m ein und bestätigen den Befehl. Daraufhin öffnet sich ein Editor mit der Datei.
Als Beispiel erstellen wir das Skript MyScript.m mit folgendem Inhalt:

A = rand(100);
d = det(A)

Es wird also eine zufällige 100 x 100 Matrix A angelegt und die Determinante d berechnet.
In Matlab können wir nun das Skript durch Eingeben des Namens MyScript aufrufen. Daraufhin wird das Skript ausgeführt und die Variable d ausgegeben. Was ausgegeben wird können mir mit dem Zeichen ";" steuern. Beenden wir eine Zeile mit dem Semikolon, wird diese ohne Ausgabe ausgeführt. Lassen wir dieses weg, wird das Ergebnis der Operation ausgegeben. Lassen wir also in der 1. Zeile des Skripts das Semikolon weg, wird die komplette Matrix ebenfalls ausgegeben.

Als zweites Beispiel legen wir nun eine Funktion an, welche den Durchschnitt über die übergebenen Werte berechnet und zurückgibt. Dafür geben wir edit MyFunction.m ein.
Als Inhalt geben wir ein:

function [y] = MyFunction(x)
y = sum(x);
y = y / numel(x);
end

Wie man sieht, beginnt eine Funktionsdeklaration mit dem Schlüsselwort function und endet mit end. In eckigen Klammern geben wir dann zuerst die Rückgabeparameter an - hier wird nur y zurückgegeben, mehrere Parameter werden einfach per Komma getrennt. Nach dem Gleichheitszeichen geben wir den Namen der Funktion (welcher mit dem Dateinamen übereinstimmten sollte) sowie die Eingabeparameter in runden Klammern an - hier nur x.
In der Funktion summieren wir dann über x (wir erwarten also einen Vektor) und teilen durch die Anzahl der Elemente in x. Das Ergebnis gibt die Funktion in y zurück. In Matlab können wir dann die Funktion wie folgt aufrufen und ihren Rückgabewert benutzten: test = MyFunction([1,2,3,])
(Hinweis: In Matlab lässt sich der Durchschnittswert natürlich einfacher berechnen - zum Beispiel mit der vordefinierten Funktion mean(). Ich habe dieses Beispiel nur zur Erläuterung genommen.)

Donnerstag, 17. September 2015

Matlab Tutorial Teil 3 - Lineare Gleichungssysteme lösen

Im heutigen Teil möchte ich zeigen, wie man mit Matlab lineare Gleichungssysteme löst.
Ein lineares Gleichungssystem hat die Form A * x = b, wobei A die Koeffizienten Matrix und b die rechte Seite des Gleichungssystems ist. Wir möchten dann nach x auflösen.
Hierfür müssen wir einfach A und x in Matlab eingeben und dann den Befehl

x = linsolve(A, b)

aufrufen.

Ein Beispiel:
Wir möchten das LGS

x1   + x2     + x3   = 5
2x1             + 3x3 = 2
          10x2 - x3    = 1

lösen.
Also geben wir in Matlab die Koeffizienten Matrix (A = [1, 1, 1; 2, 0, 3; 0, 10, -1]) sowie den Vektor b (b = [5;2;1]) ein.
Nach Eingabe von x = linsolve(A, b) gibt uns Matlab die richtige Antwort

x =

   15.6250
   -0.8750
   -9.7500

zurück.

Mittwoch, 16. September 2015

Matlab Tutorial Teil 2 - Vektoren und Matrizen

Nachdem ich im vorigen Post eine kleine Einführung in die Programmiersprache Matlab gegeben habe, möchte ich mit diesem Post nun daran anknüpfen.
Im heutigen Teil geht es um die Verwendung von Matrizen und Vektoren, für welche die Sprache besonders ausgelegt ist, und welche sehr bequem gehandhabt werden können.
Eine Matrix wird in eckigen Klammern definiert, Einträge einer Zeile werden per Komma getrennt, Zeilen durch Semikolon, also zum Beispiel: A = [1, 2; 3, 4]
Vektoren sind natürlich nur Spezialfälle einer Matrix, nämlich eindimensionale Matrizen. Dementsprechend können sie mit V1 = [1, 2] (Zeilenvektor) oder V2 = [3;4] (Spaltenvektor) definiert werden - im folgenden wird nur noch der Begriff Matrix verwendet.
Es gibt verschiedene vordefinierte Matlab Funktionen mit welchen bestimmte Matrizen erzeugt werden können: Hier möchte ich ones(), zeros() und rand() erwähnen.
ones(a, b) erzeugt eine a x b Matrix (a Zeilen, b Spalten) bei der alle Einträge 1 sind. Dies kann auch für höhere Dimensionen gemacht werden, wird nur ein Parameter übergeben wird eine 2-dimensionale quadratische Matrix erzeugt.
zeros() und rand() tun das gleiche, zeros() füllt die resultierende Matrix mit Nullen, rand() mit zufälligen Einträgen zwischen 0 und 1.
Mit Matrizen kann wie gewohnt umgegangen werden. Mit +, -, * können also Matrixaddition, -subtration und -multiplikaton durchgeführt werden. * ist überladen und kann auch für eine Skalarmultiplikation zwischen einem Skalar (einer Zahl) und einer Matrix verwendet werden.
In der Matlab Dokumentation sind alle möglichen Funktionen zum Umgang mit Matrizen aufgeführt, wie zum Beispiel die Rangbestimmung (rank()) und und und.
Eine weitere interessante Funktion beim Umgang mit Matrizen ist die Extraktion von Untermatrizen.
Wir können wie folgt die Untermatrix einer Matrix A, welche aus den Zeilen a bis b und Spalten c bis d besteht auswählen: A(a:b, c:d)
Wir können eine Dimension komplett wählen indem wir dafür nur den Doppelpunkt übergeben: A(:, c:d)
Mit der Funktion sum() können wir so zum Beispiel bequem über die Spalten / Zeilen einer Matrix summieren, für Zeile i lautet der Befehl dafür: sum(A(1,:))
Im nächsten Post werde ich eine weitere interessante Anwendung zeigen, dort geht es um das Lösen von Gleichungssystemen.

Mittwoch, 2. September 2015

Matlab Tutorial Teil 1 - Einführung

Nach Ausflügen in die App Programmierung für Android und die Internetprogrammierung mit PHP möchte ich nun das Themengebiet dieses Blogs noch ein wenig erweitern und einen Einblick in die Programmiersprache Matlab geben. Diese ist eine kommerzielle Software der Firma The MathWorks und eignet sich besonders zur Lösung mathematischer Probleme und deren Visualisierung. Matlab ist sehr beliebt und wird zum Beispiel auch in der Industrie sehr häufig eingesetzt, dies liegt unter anderem an der Einfachheit der für mathematische Anwendungsfälle entwickelten Programmiersprache, schnell können Probleme modelliert und Lösungen getestet werden. Ein Tradeoff ist dabei jedoch die Performance, optimierter C++ Code zum Beispiel kann um einen Faktor von 50 - 100 schneller sein.
Zuerst muss Matlab installiert werden. Wie bereits erwähnt ist die Software kommerziell, glücklicherweise gibt es jedoch zum Beispiel an vielen Universitäten kostenlose Versionen für Studenten. In diesem Fall kann Matlab von diesen Quellen installiert werden. Nach erfolgreichem Abschluss und Aktivierung der Lizenzen kann man das Programm starten und sollte in etwa das folgende Bild sehen:



Im mittleren Fenster, dem Command Window können wir nun direkt Befehle eingeben und so Code ausführen. Hier kann man schnell die Einfachheit der Skriptsprache Matlab erkennen: Variablen müssen zum Beispiel nicht deklariert werden, es gibt keine Typen, Zahlen, Matrizen etc. können einfach verwendet werden.
Geben wir einmal A = 1 ein und bestätigten mit Enter, danach B = 2 und bestätigen ebenfalls. In der Matlab Umgebung werden nun A und B als die entsprechenden Zahlen gespeichert, welche wir dann benutzen können, zum Beispiel mittels A + B.
Genauso können wir auch Matrizen (und damit Vektoren) definieren: Werte in einer Zeile werden mit Komma getrennt, Zeilen mit Semikolon, die ganze Matrix mit eckigen Klammern umgeben.
C = [1, 2; 3, 4] definiert die entsprechende Matrix, nach Drücken von Enter sehen wir diese dann auch grafisch im Ausgabefenster.
Mit diesen Zahlen und Matrizen können wir nun wie gewohnt weiter rechnen, man probiere zum Beispiel einmal B * C + C für die Durchführung einer Skalarmultiplikation und einer Matrixaddition.
Dies soll für eine erste Einführung genügen, demnächst folgen weitere Posts.

Samstag, 22. August 2015

Posts auf Blogger.com veröffentlichen

Im Januar schrieb ich einen Post über das Thema, wie man Posts automatisiert auf Blogger.com veröffentlicht. Dafür wurde jedoch die API v2.0 verwendet, welche nun veraltet ist, daher möchte ich hier zeigen wie man dies mit der API v3.0 umsetzt.
Leider habe ich noch keinen Weg (und ich weiß nicht ob es einen gibt) gefunden wie man Posts komplett ohne Benutzer Interaktion veröffentlicht, in dieser Version muss er sich anmelden und einen Code kopieren.
Zusammengefasst sieht der Ablauf in dieser Version wie folgt aus:
Über die Google Developer Console muss ein neues Projekt erstellt werden und die Blogger API aktiviert werden. Informationen über diesen Schritt und generelle Informationen über die API können hier gefunden werden. Dann, um die API nutzen zu können, müssen wir uns mittels OAuth 2.0 authentifizieren. Dafür müssen wir eine Anfrage an Google senden. Wir tuen dies in dem wir eine bestimmte URL im Browser aufrufen, als Parameter übergeben wir unter anderem die ID unseres Projekts, den sogenannten Scope (Gültigkeitsbereich) etc. Der User loggt sich dann im Browser ein und erhält einen Autorisierungs Code. Diesen Code sendet das Programm dann mittels einem HTTP Request an Google und erhält ein Token zurück. Damit können wir schließlich die API aufrufen und Posts auf Blogger posten. Dieser Teil ist hier beschrieben.

Nach diesem groben Überblick jetzt zur konkreten Implementierung: Zuerst erstellen wir ein neues Projekt in der Google Developer Console. Dann suchen wir die API Blogger API v3 im Menu APIs und aktivieren diese.
In unserem C# Programm müssen wir nun zuerst eine URL für den initialien Request aufrufen. Die benötigte URL ist https://accounts.google.com/o/oauth2/auth. Ein Parameter den wir übergeben ist scope, dieser beschreibt für welche Anwendung wir uns authentifizieren wollen. Wir wählen dafür https://www.googleapis.com/auth/blogger. Der nächste Parameter ist die Redirect URL (redirect_uri), welche beschreibt wohin die Antwort gesendet wird. Wird diese auf urn:ietf:wg:oauth:2.0:oob gesetzt, wird die Antwort in dem geöffneten Browser angezeigt.
Mittels response_type=code stellen wir ein einen Code zurück zu kriegen. Als letzten Parameter setzen wir client_id auf die ID unseres erstellten Projekts in der Google Developer Console. Dies ist NICHT die Projekt ID welche auf der Hauptseite angezeigt wird, sondern um die Client ID auszulesen muss man unter APIs & auth - Credentials auf Add Credentials - OAuth 2.0 client ID klicken (falls nicht bereits getan). Dann wählen wir Other aus (da wir eine native Applikation entwickeln) und klicken auf Create - dann erhalten die wir Client ID.
Insgesamt sollte die aufgerufene URL so aussehen:

https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/blogger&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&client_id=client-id


Wir benutzen Process.Start mit der URL um den Standardbrowser mit dieser zu öffnen. In diesem wird dem Benutzer dann ein Login und Zustimmungsbildschirm präsentiert. Falls er auf akzeptieren klickt, wird ein Erfolgs Code präsentiert. Wir kopieren diesen.
Mit diesem Code können wir ein Access Token für die Nutzung der Blogger API erhalten. Dafür müssen wir einen HTTP Post Request durchführen. Die URL die wir aufrufen müssen ist https://www.googleapis.com/oauth2/v3/token, als Parameter übergeben wir den erhaltenen Code (code), die Client ID (client_id), die Redirect URL (redirect_uri, die gleiche wie vorher), den Grant Type (grant_type=authorization_code - beschreibt wie wir uns authentifizieren wollen) und das Client Secret (client_secret). Das letztere ist sichtbar wenn man auf Client ID unter Credentials klickt.
Mit der Funktion HTTPPost(), welche im verlinkten Post beschrieben ist, sieht das wie folgt aus:
            string Code = "code=" + Code1 + "&";
            string ID = "client_id=id&";
            string uri = "redirect_uri=urn:ietf:wg:oauth:2.0:oob&";
            string grant = "grant_type=authorization_code&";
            string secret = "client_secret=secret";

            string Code2 = HTTPPost("https://www.googleapis.com/oauth2/v3/token", Code + ID + uri + grant + secret);

Wir lesen die Antwort des Servers aus da diese (im Erfolgsfall) das Access Token enthält. Die Antwort wird im JSON Format zurückgegeben, wir benutzten die Library Newtonsoft.Json um diese zu interpretieren. Vielleicht schreibe ich demnächst einen Post über die Library, fürs Erste verweise ich hier nur auf diesen Post, wo sie auch benutzt wird.
Also erhalten wir das Access Token mit dem folgenden Code, wobei AccessToken eine selber erstellte Klasse mit den gewünschten Attributen ist:

AccessToken JsonAccToken = (AccessToken)JsonConvert.DeserializeObject(Code2, typeof(AccessToken));
string StrAccToken = JsonAccToken.access_token;

Mit diesem Token können wir nun die API benutzen um Posts auf Blogger zu veröffentlichen. Wir benutzen einen WebRequest um den entsprechenden POST Request an den Blogger Server zu senden. Als Ziel Adresse setzen wir https://www.googleapis.com/blogger/v3/blogs/.
Als nächsten setzen wir dann den entsprechenden Content Type, den Authentifizierungs Header etc:

var http = (HttpWebRequest)WebRequest.Create(new Uri("https://www.googleapis.com/blogger/v3/blogs/" + sid + "/posts/"));
http.Accept = "application/json";
http.ContentType = "application/json";
http.Method = "POST";
http.Headers.Add("Authorization""Bearer " + token);

sid ist die ID des Blogs auf welchem wir veröffentlichen wollen. Als nächstes beschreiben wir den Post im JSON Format, dafür schreiben wir ihn zuerst als String und konvertieren ihn dann.

var vm = new { kind = "blogger#post", blog = new { id = sid }, title = stitle, content = scontent };
var dataString = JsonConvert.SerializeObject(vm);
string parsedContent = dataString;

Im Code bezeichnet stitle den Titel des Posts, scontent den Inhalt (welcher im HTML Format erwartet wird).
Schließlich laden wir dies mit dem WebRequest hoch.
Der komplette Code sieht so aus:

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.Net;
using System.IO;
using Newtonsoft.Json;

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

        private void Form1_Load(object sender, EventArgs e)
        {
            System.Diagnostics.Process.Start("https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/blogger&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&client_id=client-id");
        }

        private string HTTPPost(string url, string postparams)
        {
            string responseString = "";

            // performs the desired http post request for the url and parameters
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            // request.CookieContainer = Cookie; // explicitely use the cookiecontainer to save the session

            string postData = postparams;
            byte[] data = Encoding.UTF8.GetBytes(postData);

            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            request.ContentLength = data.Length;

            using (var stream = request.GetRequestStream())
            {
                stream.Write(data, 0, data.Length);
            }

            var response = (HttpWebResponse)request.GetResponse();

            responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();

            return responseString;

        }

        private void Form1_Click(object sender, EventArgs e)
        {
            string Code = "code=" + textBox1.Text + "&";
            string ID = "client_id=client-id&";
            string uri = "redirect_uri=urn:ietf:wg:oauth:2.0:oob&";
            string grant = "grant_type=authorization_code&";
            string secret = "client_secret=secret";

            string Code2 = HTTPPost("https://www.googleapis.com/oauth2/v3/token", Code + ID + uri + grant + secret);

            AccessToken JsonAccToken = (AccessToken)JsonConvert.DeserializeObject(Code2, typeof(AccessToken));
            string StrAccToken = JsonAccToken.access_token;

            JSONPublish(BlogID, "Testpost""This is a Test.", StrAccToken);
        }

        private void JSONPublish(string sid, string stitle, string scontent, string token)
        {
            var http = (HttpWebRequest)WebRequest.Create(new Uri("https://www.googleapis.com/blogger/v3/blogs/" + sid + "/posts/"));
            http.Accept = "application/json";
            http.ContentType = "application/json";
            http.Method = "POST";
            http.Headers.Add("Authorization""Bearer " + token);

            var vm = new { kind = "blogger#post", blog = new { id = sid }, title = stitle, content = scontent };
            var dataString = JsonConvert.SerializeObject(vm);
            string parsedContent = dataString;

            Byte[] bytes = Encoding.UTF8.GetBytes(parsedContent);

            Stream newStream = http.GetRequestStream();
            newStream.Write(bytes, 0, bytes.Length);
            newStream.Close();

            var response = http.GetResponse();

            var stream = response.GetResponseStream();
            var sr = new StreamReader(stream);
            var content = sr.ReadToEnd();
        }

        public class AccessToken
        {
            [JsonProperty(PropertyName = "access_token")]
            public string access_token { get; set; }
        }
    }
}

Donnerstag, 30. Juli 2015

.Net und C# Alternative zu Matlab: ILNumerics.

Hinweis: Für diesen Post wurde ich von der ILNumerics GmbH bezahlt, konnte und sollte aber den Inhalt selber frei bestimmen.

Im heutigen Post möchte ich die Klassenbibliothek ILNumerics vorstellen und meine Erfahrungen damit teilen.
ILNumerics ist eine Klassenbibliothek für .Net mit dem Ziel numerische Berechnungen, Plots etc. einfacher und schneller in .Net implementieren zu können. Natürlich könnte man dies alles prinzipiell auch mit .Net Mitteln umsetzen, was allerdings sehr umständlich sein kann (man denke allein an die Darstellung von Vektoren, Matrix Multiplikation etc.). Mit ILNumerics steht nun eine Matlab sehr ähnliche Spracherweiterung bereit, mit welcher komplexere Algorithmen effizient implementiert werden können. Im folgenden werde ich nun, wie gesagt, eine kleine Einführung in dieses Thema geben und das kostenpflichtige Produkt testen, welches mir insgesamt sehr gut gefallen hat.

Fangen wir mit der Installation an: Heruntergeladen werden kann die Bibliothek über die Homepage. Erhältlich ist kostenlos nur eine Trial Version, diese Tatsache und der Preis ist der einzige Negativpunkt meiner Meinung nach. Dieser ist relativ hoch, anfangend von 89 Euro pro Monat, also wahrscheinlich für den Freizeitnutzer nicht geeignet. Sehr wohl jedoch für Universitäten, Firmen o.ä., hier kann ich mir ILNumerics als Alternative zu Matlab vorstellen, insbesondere da ich persönlich sehr gerne in C# und .Net programmiere. Eine kostenlose Version für Studenten wäre sehr zu begrüßen.
Nach der Installation kann die Trial Version getestet werden bzw. die Version aktiviert werden.
Weitere Informationen hierzu gibt es auf der Support Seite. Auf dieser gibt es auch ein Tutorial und eine genauere Dokumentation, welche auch mir als Referenz diente.

Die Bibliothek beinhaltet im Wesentlichen zwei Komponenten: Die Computation und die Visualization Engine. Zuerst werde ich die Verwendung der Computation Engine erläutern, dann die der Visualization Engine.

Um die Computation Engine ganz einfach einzubinden, legen wir ein neues Windows Projekt an und klicken dann im Menü auf Projekt - Neues Element hinzufügen. Im sich öffnenden Fenster wählen wir "Computing Module" aus und fügen damit eine neue .cs Datei in Form eines Computing Modules dem Projekt hinzu. In dieser sind bereits einige Beispielmethoden implementiert, die die Verwendung von ILNumerics demonstrieren, welche aber natürlich gelöscht werden können. Die Main - Methode lassen wir aber, da wir das Computing Module als Einstiegspunkt in das Programm verwenden möchten. Dies müssen wir dem Programm mitteilen, da auch die angelegte Form Klasse eine Main - Methode besitzt. Dies tun wir, in dem wir unter Projekteigenschaften - Anwendung Startobjekt auf das Computation Module setzen.
Der Code der Datei Computing Module1.cs sollte so aussehen:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ILNumerics;
using System.IO;

namespace IlNumericsTest
{

    public class Computing_Module1 : ILMath
    {
        public static void Main(params string[] parameter)
        {
        }
    }

}

Fangen wir mit ein paar leichten Operationen zur Eingewöhnung an.
Matrizen werden über die ILNumerics Klasse ILArray verwaltet. Mit diesen lassen sich mit +. -, *, / die erwarteten Rechenoperationen durchführen, nämlich Matrixaddition / -subtraktion und komponentenweise Multiplikation / Division. Der folgende Code erzeugt 3 Matrizen der Größe 10 x 10, die erste wird mit Einsen gefüllt, die zweite mit Nullen und die dritte mit zufälligen Werten zwischen 0 und 1. Dann wird auf der Konsole A + C ausgegeben sowie A * (-1).

ILArray<double> A = ones(10, 10);
ILArray<double> B = zeros(10, 10);
ILArray<double> C = rand(10, 10);
Console.WriteLine(A + C);
Console.WriteLine(-A);

Nun ein paar etwas komplexere Beispiele, ich möchte ein paar Operationen auf Graphen und Netzwerken zeigen. Wer Graphen nicht kennt sei auf Wikipedia verwiesen, diese sind jedenfalls eine oft benutzte Datenstruktur in der Informatik. Das Fachgebiet Web Science befasst sich insbesondere mit der Erforschung und Analyse von Netzwerken (wie dem Internet), dabei werden Graphen zur Repräsentation der Daten verwendet, welche riesige Ausmaße annehmen können. Auf Grund der Themenrelevanz (oft wird dabei z.B. mit Matlab gearbeitet) habe ich mich entschieden, aus diesem Bereich ein paar Beispiele zu zeigen, wie zum Beispiel den PageRank Algorithmus von Google.
Zuerst aber eine Funktion zum Einlesen von Daten:

            var file = File.ReadAllText("Graph.txt");
            ILArray AdjMatrix = csvread(file);

In der ersten Zeile wird die Datei in einen String gelesen, welcher dann mit der ILNumerics Funktion csvread() in eine Matrix gelesen wird. csvread() trennt die eingelesenen Werte (zum Beispiel) nach Komma. Die Eingabedatei soll die kommagetrennte Adjazenzmatrix eines Graphen enthalten, welche eine 1 an Position i, j besitzt falls die Knoten i und j mit einer Kante verbunden sind, ansonsten 0. Für einen Graph mit 4 Knoten könnte die Datei so aussehen:

0, 1, 0, 0
1, 0, 1, 1
0, 1, 0, 1
0, 1, 1, 0

Nun möchten wir aus der Adjazenzmatrix die Laplace Matrix bestimmen, welche den Grad von Knoten i im i-ten Diagonaleintrag enthält und -1 an Position i, j falls die Knoten i, j verbunden sind.
Dafür legen wir eine neue Funktion an. Da ILNumerics sehr auf Performance / Speichermanagement achtet (achten muss wenn es große Daten handhaben will), sind hier ein paar Dinge zu beachten. Zum einen gibt es nicht nur den vorgestellten Typ ILArray, sondern auch noch ILInArray, ILOutArray und ILRetArray (und generell noch andere Typen als Array). Für genaueres sei auf die Dokumentation verwiesen, hier nur kurz, ILInArray wird zum Beispiel für Eingabeparameter verwendet, ist nicht änderbar und wird direkt nach Verlassen des entsprechenden Scopes gelöscht (eben aus Speicher / Performance Gründen). Dafür müssen wir auch jede Funktion mit einem neuen Scope umschließen, welcher die Input Parameter enthält.
Die Funktion zur Berechnung der Laplace Matrix sieht so aus:

public static ILRetArray<double> CalcLaplacian(ILInArray<double> AdjMatrix)
{
    using (ILScope.Enter(AdjMatrix))
    {
        ILArray<double> Degrees = sum(AdjMatrix);
        ILArray<double> LaplacianMatrix = Degrees * eye(AdjMatrix.Length, AdjMatrix.Length);
        LaplacianMatrix = LaplacianMatrix - AdjMatrix;
        return LaplacianMatrix;
    }
}

Der Eingabeparameter ist die Adjazenzmatrix. In der ersten Zeile im Scope wird der Grad der Knoten ausgerechnet, was mit der Funktion sum() getan wird. Diese summiert alle Spalten der Matrix auf und gibt einen Vektor mit dem Ergebnis aus, welcher genau der Gradfolge entspricht. eye() erzeugt eine Matrix mit Einsen auf der Diagonalen, Multiplizieren mit dem Gradvektor und Subtrahieren von der Adjazenzmatrix liefert genau die gewünschte Matrix.

Als nächstes Beispiel berechnen wir die Dichte eines Netzwerks. Diese ist eine wichtige Kennzahl in Netzwerken, sie berechnet sich als Anzahl der vorhandenen Kanten durch die Anzahl der möglichen Kanten und gibt somit eben an, wie dicht bzw. zentralisiert ein Netzwerk ist. Durch eine doppelte Summation über die Matrixdimension lässt sich die Dichte ganz leicht berechnen:

public static ILRetArray<double> CalcDensity(ILInArray<double> AdjMatrix)
{
    using (ILScope.Enter(AdjMatrix))
    {
        ILArray<double> NrLinks = sum(sum(AdjMatrix));
        return (double)NrLinks / (AdjMatrix.Length * (AdjMatrix.Length - 1));
    }
}

Nun zu einem Algorithmus, welcher seine Erfinder reich und weltberühmt gemacht hat, im Kern im Prinzip aber so einfach ist, dass wir ihn hier jetzt umsetzen (im Kern, natürlich gehört noch viel mehr dazu als die paar Zeilen Code). Die Rede ist von Googles Pagerank Algorithmus, für lange Zeit die Basis für das Ranking von Webseiten bei der Google Suche. Der Algorithmus weist jeder Seite einen Pagerank (eine Popularität) zu, welcher sich aus der Summe der Pageranks der zu der Seite verlinkenden Seiten zusammensetzt. Die Formel zur Berechnung ist im verlinkten Wikipedia Artikel erklärt, in der Praxis wird der Pagerank jedoch iterativ wie folgt approximiert: Starte mit einem beliebigen Pagerank Vektor P, dann berechnet sich der neue Pagerank zu P = (1 - d) * e * A'T*P. Hierbei ist d ein Dämpfungsfaktor (z.B. 0.5), e der Einheitsvektor und A' die Matrix die aus der Adjazenzmatrix A entsteht, indem alle Zeilen mit nur Nullen durch Zeilen mit Einträgen 1/n (n = Anzahl der Knoten) ersetzt wird. Dies wiederholen wir so oft, bis sich neuer und alter Pagerank genügend nah angenährt haben, das Verfahren also pro Iteration nicht mehr viel verändert. In ILNumerics sieht das wie folgt aus:

        public static ILRetArray<double> CalcPageRank(ILArray<double> AdjMatrix)
        {
            using (ILScope.Enter(AdjMatrix))
            {
                ILArray<double> Degrees = sum(AdjMatrix.T);
                double epsilon = 0.00001;
                double d = 0.5;
                for (int i = 0; i < AdjMatrix.Length; i++)
                {
                    for (int j = 0; j < AdjMatrix.Length; j++)
                    {
                        if (Degrees[i] != 0)
                            AdjMatrix[i, j] /= Degrees[i];
                    }
                    if (AdjMatrix["0", ":"].Equals(zeros(1, AdjMatrix.Length)))
                        AdjMatrix["0", ":"] = ones(AdjMatrix.Length) / AdjMatrix.Length;
                }
                ILArray<double> POld = zeros(AdjMatrix.Length);
                ILArray<double> PNew = ones(AdjMatrix.Length);

                do
                {
                    POld = PNew;
                    PNew = (1 - d) * ones(AdjMatrix.Length) + d * multiply(AdjMatrix.T, POld);
                }
                while (norm(POld - PNew, 1) > epsilon);

                return PNew;
            }
        }

Mit der vorher beschriebenen Methode muss also zuerst die Adjazenzmatrix eines beliebigen Graphen eingelesen werden, die Funktion berechnet dann für jeden Knoten im Graph den Pagerank und gibt den Vektor der Pageranks zurück. Wie man sieht, ist auch die Auswahl von Untermatrizen wie in Matlab möglich. Matrix["a:b", "c:d"] wählt die Untermatrix aus, welche aus den Zeilen a - b und den Spalten c - d von Matrix besteht.

Allerdings sollte man die Pagerank Berechnung so nicht in ILNumerics implementieren. Ich habe den Code nur erst in diesem Stil geschrieben, um einen intuitiven Einstieg zu geben, und damit aufzuzeigen, wie man die Fähigkeiten von ILNumerics besser ausnutzt.
Generell empfiehlt sich die Verwendung von ILNumerics eigenen Funktionen und die Vermeidung von selber gebauten, wie for - Schleifen über große Matrizen etc. So verwenden wir statt dem iterieren durch die Matrix und Testen der Zeilen auf 0 den ILLogical Datentyp. Dieser liefert uns einen Vektor, welcher angibt, welche Elemente aus einem gegebenen Vektor eine bestimmte Bedingung erfüllen. Wir wählen als Bedingung Degrees = 0, wählen dann die Untermatrix mit den entsprechenden Zeilen aus und ersetzen diese durch 1/n. Das gleiche tun wir, um Einträge zu Knoten mit Grad ungleich 0 durch n zu teilen. Desweiteren haben wir unnötige Rechenoperationen aus der Rechenschleife herausgezogen, wie das Generieren eines Vektors von Einsen und das Transponieren der Matrix. Der entstandene, deutlich schlankere und schnellere Code sieht so aus:

        public static ILRetArray<double> CalcPageRank(ILArray<double> AdjMatrix)
        {
            using (ILScope.Enter(AdjMatrix))
            {
                ILArray<double> Degrees = sum(AdjMatrix, 1);
                double epsilon = 0.00001;
                double d = 0.5;

                ILLogical dummy = Degrees == 0;

                AdjMatrix[dummy, full] = 1.0 / AdjMatrix.Length;
                dummy = Degrees != 0;

                AdjMatrix[dummy, full] = AdjMatrix[dummy, full] / Degrees[dummy];
                AdjMatrix = AdjMatrix.T;

                ILArray<double> POld = zeros(AdjMatrix.Length);
                ILArray<double> PNew = ones(AdjMatrix.Length);
                ILArray<double> ILOnes = (1.0 - d) * ones(AdjMatrix.Length);

                do
                {
                    POld = PNew;
                    PNew = ILOnes + d * multiply(AdjMatrix, POld);
                }
                while (norm(POld - PNew, 1) > epsilon);

                return PNew;
            }
        }

Dieses Verfahren in Matlab implementiert benötigt bei einem 20 MB großen Graphen (ca. 3000 Knoten) etwa 5s, ILNumerics ebenfalls. Bei so geringen Laufzeiten lassen sich natürlich keine verlässlichen Performancemessungen durchführen, aber ich denke diese liegt im vergleichbaren Bereich. Laut Angaben der Hersteller hat ILNumerics einen etwas größeren Overhead, soll aber bei längeren Ausführungszeiten wesentlich schneller sein.

Um die Computing Engine abzuschließen, hier noch ein Beispiel wie sich ein lineares Gleichungssystem lösen lässt:

            ILArray<double> A = ILMath.zeros(3, 3);
            A["0;:"] = new double[] { 1, 2, 1 };
            A["1;:"] = new double[] { 3, 4, 0 };
            A["2;:"] = new double[] { -1, 0, 1 };

            ILArray<double> B = new double[] { 5, 4, 7};

            ILArray<double> x = ILMath.linsolve(A, B);

Kommen wir nun zur Visualization Engine. Hierbei gibt es natürlich unglaublich viele Möglichkeiten und Einstellungen, den gewünschten Plot zu erzeugen, deswegen werde ich hier nur eine kurze Einführung geben und für den Rest auf die Online Dokumentation verweisen.
Wie bei der Computation Engine folgen wir hier dem Quick Start Guide und legen ein neues Windows-Form Projekt an. Dann fügen wir mittels Projekt - Neues Element hinzufügen eine Plotting Form hinzu. In der Datei Program.cs ändern wir die Zeile Application.Run(new Form1()); zu Application.Run(new Plotting_Form1());, um dem Programm mitzuteilen, dass es das Plotting Formular starten soll.
Dieses enthält bereits Demo Plots, wir löschen aber erst einmal den Code um von Grund auf einen neuen Plot zu erstellen. Die Plotting Form stellt ein Steuerelement names ilPanel1 bereit, in dessen Load() Funktion zeichnen wir beim Starten des Formulars einen einfachen Linienplot mit 6 Punkten:

        private void ilPanel1_Load(object sender, EventArgs e)
        {
            var scene = new ILScene();
            // create some data

            ILArray<float> A = new float[] { 1, 2, 3, 4, -1, -2 };
            // add a plot cube
            scene.Add(new ILPlotCube {
  new ILLinePlot(A)
});
            ilPanel1.Scene = scene;
        }

Grundbestandteil der Plots sind sogennante scenes. Wir legen hier eine neue an und fügen dieser dann einen neuen Linienplot mit den angegebenen Daten hinzu. Das Ergebnis sieht so aus:


Standardmäßig kann der Zeichenbereich verschoben, gezoomt etc. werden. Hierfür muss der Plot per Code neugezeichnet werden.









Dies soll schon genügen, hier noch ein paar interessante Plots von der Homepage, die sich auch sehr leicht implementieren lassen:



Als Zusammenfassung kann ich noch einmal sagen, dass ich als Manko den Preis sehe, mir aber das Produkt insgesamt sehr gut gefallen hat, es bietet ein schnelles und einfach zu bedienendes Rechen- und Visualisierungstoolkit und ich auch von der Geschwindigkeit des Codes überzeugt bin. Ich sehe darin eine echte Alternative zu Matlab und würde mich freuen, wenn diese öfter eingesetzt wird.

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.

Freitag, 17. Juli 2015

MIDI Töne mit C# erzeugen

Nachdem ich in den vorigen Posts das Erzeugen grundlegender musikalischer Töne mit Beep erklärt habe, möchte ich heute nun den Klang deutlich verbessern, was wir mit dem MIDI Format tun.
MIDI ist ein Standard zum Austausch musikalischer Steuerinformationen. Hauptsächlich wurde es zur Kommunikation von Synthesizern entworfen, es besteht aus Nachrichten die das Spielen bestimmter Töne beschreiben. Mit einer externen Bibliothek können wir in C# ganz leicht MIDI Töne abspielen, welche auch wirklich gut klingen, was mich sehr überrascht und fasziniert hat - mit ein paar Codezeilen kann man wirklich Lieder abspielen.

Wir verwenden dafür die MIDI Bibliothek midi-dot-net. Diese muss heruntergeladen werden, und dann ist die Datei "midi.dll" ins Projekt einzubinden (Projekt - Verweis hinzufügen). Nach using Midi; kann dann ein OutputDevice angelegt werden, welches wir mit InstalledDevices[0] auf unseren Standardlautsprecher schalten. Mittels SendNoteOn() können wir dann eine Note spielen. Hierzu müssen wir als ersten Parameter einen Channel übergeben, als zweiten die Note und als dritten einen int - Wert für die Anschlagstärke der gedachten Keyboard Taste. Die Note wird als Objekt von der Aufzählung Pitch angegeben, welche ein breites Spektrum von Tönen enthält.
Ich fand jedoch den Ansatz aus dem letzten Post der Benutzung der Klaviertasten ganz schön, deswegen benutzen wir das hier: Wir konvertieren uns die Aufzählung in ein Pitch Array und suchen uns dann den zu der gedrückten Taste gehörigen Ton heraus (der gewünschte Ton der Taste n ist der Eintrag n + 21).
Der Ton wird solange gespielt, bis ein anderer Befehl kommt, z.B. können wir mit SendNoteOff() den Ton ausschalten. Wir warten einfach mittels Sleep() die entsprechende Tonlänge und spielen dann den nächsten.
Das folgende Programm spielt die beidem im vorigen Post gezeigten Hymnen mit MIDI Tönen ab:

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 Midi;
using System.Threading;

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

        private void Form1_Load(object sender, EventArgs e)
        {
            PlayAmericanAnthem();
            PlayGermanAnthem();
        }

        OutputDevice outputDevice;

        public void PlayNote(int key, double duration)
        {
            Pitch Note = ((Pitch[])Enum.GetValues(typeof(Pitch)))[key + 21];
            outputDevice.SendNoteOn(Channel.Channel1, Note, 80);
            Thread.Sleep((int)duration);
        }

        public void PlayGermanAnthem()
        {
            outputDevice = OutputDevice.InstalledDevices[0];
            outputDevice.Open();

            PlayNote(31, 700);
            PlayNote(33, 300);

            PlayNote(35, 500);
            PlayNote(33, 500);
            PlayNote(36, 500);
            PlayNote(35, 500);

            PlayNote(33, 250);
            PlayNote(30, 250);
            PlayNote(31, 500);
            PlayNote(40, 500);
            PlayNote(38, 500);

            PlayNote(36, 500);
            PlayNote(35, 500);
            PlayNote(33, 500);
            PlayNote(35, 250);
            PlayNote(31, 250);

            PlayNote(38, 800);
            outputDevice.Close();
        }

        public void PlayAmericanAnthem()
        {
            outputDevice = OutputDevice.InstalledDevices[0];
            outputDevice.Open();

            PlayNote(20, 300 * 1.3);
            PlayNote(17, 170 * 1.3);

            PlayNote(13, 333 * 1.3);
            PlayNote(17, 333 * 1.3);
            PlayNote(20, 333 * 1.3);

            PlayNote(25, 666 * 1.3);
            PlayNote(29, 200 * 1.3);
            PlayNote(27, 130 * 1.3);

            PlayNote(25, 333 * 1.3);
            PlayNote(17, 333 * 1.3);
            PlayNote(19, 333 * 1.3);

            PlayNote(20, 666 * 1.3);
            PlayNote(20, 170 * 1.3);
            PlayNote(20, 170 * 1.3);

            PlayNote(29, 450 * 1.3);
            PlayNote(27, 220 * 1.3);
            PlayNote(25, 333 * 1.3);

            PlayNote(24, 666 * 1.3);
            PlayNote(22, 200 * 1.3);
            PlayNote(24, 100 * 1.3);

            PlayNote(25, 333 * 1.3);
            PlayNote(25, 333 * 1.3);
            PlayNote(20, 333 * 1.3);

            PlayNote(17, 333 * 1.3);
            PlayNote(13, 333 * 1.3);
            outputDevice.Close();
        }
    }
}

Donnerstag, 16. Juli 2015

Klavier Tastatur

Nachdem ich im vorigen Post erklärt habe, wie man musikalische Töne mittels Beep() erzeugt, möchte ich heute einfach ein kleines Programm zeigen, welche eine rudimentäre spielbare Klaviertastatur darstellt. Die Töne werden einfach nach der Formel aus dem vorigen Post berechnet, die Oberfläche des Programms sieht so aus:










Und hier ist 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;

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

        private void PlayKey(int key)
        {
            Console.Beep((int)(Math.Pow(Math.Pow(2, 1 / (double)12), key - 49) * 440), 500);
        }

        private void button_Click(object sender, EventArgs e)
        {
            PlayKey(int.Parse(((Button)sender).Name.Substring(6)));
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            button13.Click += button_Click;
            button14.Click += button_Click;
            button15.Click += button_Click;
            button16.Click += button_Click;
            button17.Click += button_Click;
            button18.Click += button_Click;
            button19.Click += button_Click;
            button20.Click += button_Click;
            button21.Click += button_Click;
            button22.Click += button_Click;
            button23.Click += button_Click;
            button24.Click += button_Click;
            button25.Click += button_Click;
            button26.Click += button_Click;
            button27.Click += button_Click;
            button28.Click += button_Click;
            button29.Click += button_Click;
            button30.Click += button_Click;
            button31.Click += button_Click;
            button32.Click += button_Click;
            button33.Click += button_Click;
            button34.Click += button_Click;
            button35.Click += button_Click;
            button36.Click += button_Click;
            button37.Click += button_Click;
            button38.Click += button_Click;
            button39.Click += button_Click;
            button40.Click += button_Click;
            button41.Click += button_Click;
            button42.Click += button_Click;
            button43.Click += button_Click;
            button44.Click += button_Click;
            button45.Click += button_Click;
            button46.Click += button_Click;
            button47.Click += button_Click;
            button48.Click += button_Click;
            button49.Click += button_Click;
            button50.Click += button_Click;
            button51.Click += button_Click;
            button52.Click += button_Click;
            button53.Click += button_Click;
            button54.Click += button_Click;
            button55.Click += button_Click;
            button56.Click += button_Click;
            button57.Click += button_Click;
            button58.Click += button_Click;
            button59.Click += button_Click;
            button60.Click += button_Click;
        }

    }
}
Und hier der Inhalt der Datei Form1.Designer.cs:
namespace KeyboardTone
{
    partial class Form1
    {
        /// <summary>
       /// Erforderliche Designervariable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
       /// Verwendete Ressourcen bereinigen.
        /// </summary>
        /// <param name="disposing">True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Vom Windows Form-Designer generierter Code

        /// <summary>
       /// Erforderliche Methode für die Designerunterstützung.
       /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
        /// </summary>
        private void InitializeComponent()
        {
            this.button13 = new System.Windows.Forms.Button();
            this.button15 = new System.Windows.Forms.Button();
            this.button14 = new System.Windows.Forms.Button();
            this.button16 = new System.Windows.Forms.Button();
            this.button17 = new System.Windows.Forms.Button();
            this.button18 = new System.Windows.Forms.Button();
            this.button19 = new System.Windows.Forms.Button();
            this.button20 = new System.Windows.Forms.Button();
            this.button21 = new System.Windows.Forms.Button();
            this.button22 = new System.Windows.Forms.Button();
            this.button23 = new System.Windows.Forms.Button();
            this.button24 = new System.Windows.Forms.Button();
            this.button35 = new System.Windows.Forms.Button();
            this.button36 = new System.Windows.Forms.Button();
            this.button33 = new System.Windows.Forms.Button();
            this.button34 = new System.Windows.Forms.Button();
            this.button31 = new System.Windows.Forms.Button();
            this.button32 = new System.Windows.Forms.Button();
            this.button30 = new System.Windows.Forms.Button();
            this.button28 = new System.Windows.Forms.Button();
            this.button29 = new System.Windows.Forms.Button();
            this.button26 = new System.Windows.Forms.Button();
            this.button27 = new System.Windows.Forms.Button();
            this.button25 = new System.Windows.Forms.Button();
            this.button47 = new System.Windows.Forms.Button();
            this.button48 = new System.Windows.Forms.Button();
            this.button45 = new System.Windows.Forms.Button();
            this.button46 = new System.Windows.Forms.Button();
            this.button43 = new System.Windows.Forms.Button();
            this.button44 = new System.Windows.Forms.Button();
            this.button42 = new System.Windows.Forms.Button();
            this.button40 = new System.Windows.Forms.Button();
            this.button41 = new System.Windows.Forms.Button();
            this.button38 = new System.Windows.Forms.Button();
            this.button39 = new System.Windows.Forms.Button();
            this.button37 = new System.Windows.Forms.Button();
            this.button59 = new System.Windows.Forms.Button();
            this.button60 = new System.Windows.Forms.Button();
            this.button57 = new System.Windows.Forms.Button();
            this.button58 = new System.Windows.Forms.Button();
            this.button55 = new System.Windows.Forms.Button();
            this.button56 = new System.Windows.Forms.Button();
            this.button54 = new System.Windows.Forms.Button();
            this.button52 = new System.Windows.Forms.Button();
            this.button53 = new System.Windows.Forms.Button();
            this.button50 = new System.Windows.Forms.Button();
            this.button51 = new System.Windows.Forms.Button();
            this.button49 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            //
            // button13
            //
            this.button13.BackColor = System.Drawing.Color.White;
            this.button13.Location = new System.Drawing.Point(17, 42);
            this.button13.Name = "button13";
            this.button13.Size = new System.Drawing.Size(21, 113);
            this.button13.TabIndex = 0;
            this.button13.UseVisualStyleBackColor = false;
            //
            // button15
            //
            this.button15.BackColor = System.Drawing.Color.White;
            this.button15.Location = new System.Drawing.Point(38, 42);
            this.button15.Name = "button15";
            this.button15.Size = new System.Drawing.Size(21, 113);
            this.button15.TabIndex = 1;
            this.button15.UseVisualStyleBackColor = false;
            //
            // button14
            //
            this.button14.BackColor = System.Drawing.Color.Black;
            this.button14.Location = new System.Drawing.Point(29, 22);
            this.button14.Name = "button14";
            this.button14.Size = new System.Drawing.Size(17, 87);
            this.button14.TabIndex = 2;
            this.button14.UseVisualStyleBackColor = false;
            //
            // button16
            //
            this.button16.BackColor = System.Drawing.Color.Black;
            this.button16.Location = new System.Drawing.Point(50, 22);
            this.button16.Name = "button16";
            this.button16.Size = new System.Drawing.Size(17, 87);
            this.button16.TabIndex = 4;
            this.button16.UseVisualStyleBackColor = false;
            //
            // button17
            //
            this.button17.BackColor = System.Drawing.Color.White;
            this.button17.Location = new System.Drawing.Point(59, 42);
            this.button17.Name = "button17";
            this.button17.Size = new System.Drawing.Size(21, 113);
            this.button17.TabIndex = 3;
            this.button17.UseVisualStyleBackColor = false;
            //
            // button18
            //
            this.button18.BackColor = System.Drawing.Color.White;
            this.button18.Location = new System.Drawing.Point(80, 42);
            this.button18.Name = "button18";
            this.button18.Size = new System.Drawing.Size(21, 113);
            this.button18.TabIndex = 5;
            this.button18.UseVisualStyleBackColor = false;
            //
            // button19
            //
            this.button19.BackColor = System.Drawing.Color.Black;
            this.button19.Location = new System.Drawing.Point(92, 22);
            this.button19.Name = "button19";
            this.button19.Size = new System.Drawing.Size(17, 87);
            this.button19.TabIndex = 7;
            this.button19.UseVisualStyleBackColor = false;
            //
            // button20
            //
            this.button20.BackColor = System.Drawing.Color.White;
            this.button20.Location = new System.Drawing.Point(101, 42);
            this.button20.Name = "button20";
            this.button20.Size = new System.Drawing.Size(21, 113);
            this.button20.TabIndex = 6;
            this.button20.UseVisualStyleBackColor = false;
            //
            // button21
            //
            this.button21.BackColor = System.Drawing.Color.Black;
            this.button21.Location = new System.Drawing.Point(112, 22);
            this.button21.Name = "button21";
            this.button21.Size = new System.Drawing.Size(17, 87);
            this.button21.TabIndex = 9;
            this.button21.UseVisualStyleBackColor = false;
            //
            // button22
            //
            this.button22.BackColor = System.Drawing.Color.White;
            this.button22.Location = new System.Drawing.Point(121, 42);
            this.button22.Name = "button22";
            this.button22.Size = new System.Drawing.Size(21, 113);
            this.button22.TabIndex = 8;
            this.button22.UseVisualStyleBackColor = false;
            //
            // button23
            //
            this.button23.BackColor = System.Drawing.Color.Black;
            this.button23.Location = new System.Drawing.Point(133, 22);
            this.button23.Name = "button23";
            this.button23.Size = new System.Drawing.Size(17, 87);
            this.button23.TabIndex = 11;
            this.button23.UseVisualStyleBackColor = false;
            //
            // button24
            //
            this.button24.BackColor = System.Drawing.Color.White;
            this.button24.Location = new System.Drawing.Point(142, 42);
            this.button24.Name = "button24";
            this.button24.Size = new System.Drawing.Size(21, 113);
            this.button24.TabIndex = 10;
            this.button24.UseVisualStyleBackColor = false;
            //
            // button35
            //
            this.button35.BackColor = System.Drawing.Color.Black;
            this.button35.Location = new System.Drawing.Point(285, 22);
            this.button35.Name = "button35";
            this.button35.Size = new System.Drawing.Size(17, 87);
            this.button35.TabIndex = 23;
            this.button35.UseVisualStyleBackColor = false;
            //
            // button36
            //
            this.button36.BackColor = System.Drawing.Color.White;
            this.button36.Location = new System.Drawing.Point(294, 42);
            this.button36.Name = "button36";
            this.button36.Size = new System.Drawing.Size(21, 113);
            this.button36.TabIndex = 22;
            this.button36.UseVisualStyleBackColor = false;
            //
            // button33
            //
            this.button33.BackColor = System.Drawing.Color.Black;
            this.button33.Location = new System.Drawing.Point(264, 22);
            this.button33.Name = "button33";
            this.button33.Size = new System.Drawing.Size(17, 87);
            this.button33.TabIndex = 21;
            this.button33.UseVisualStyleBackColor = false;
            //
            // button34
            //
            this.button34.BackColor = System.Drawing.Color.White;
            this.button34.Location = new System.Drawing.Point(273, 42);
            this.button34.Name = "button34";
            this.button34.Size = new System.Drawing.Size(21, 113);
            this.button34.TabIndex = 20;
            this.button34.UseVisualStyleBackColor = false;
            //
            // button31
            //
            this.button31.BackColor = System.Drawing.Color.Black;
            this.button31.Location = new System.Drawing.Point(244, 22);
            this.button31.Name = "button31";
            this.button31.Size = new System.Drawing.Size(17, 87);
            this.button31.TabIndex = 19;
            this.button31.UseVisualStyleBackColor = false;
            //
            // button32
            //
            this.button32.BackColor = System.Drawing.Color.White;
            this.button32.Location = new System.Drawing.Point(253, 42);
            this.button32.Name = "button32";
            this.button32.Size = new System.Drawing.Size(21, 113);
            this.button32.TabIndex = 18;
            this.button32.UseVisualStyleBackColor = false;
            //
            // button30
            //
            this.button30.BackColor = System.Drawing.Color.White;
            this.button30.Location = new System.Drawing.Point(232, 42);
            this.button30.Name = "button30";
            this.button30.Size = new System.Drawing.Size(21, 113);
            this.button30.TabIndex = 17;
            this.button30.UseVisualStyleBackColor = false;
            //
            // button28
            //
            this.button28.BackColor = System.Drawing.Color.Black;
            this.button28.Location = new System.Drawing.Point(202, 22);
            this.button28.Name = "button28";
            this.button28.Size = new System.Drawing.Size(17, 87);
            this.button28.TabIndex = 16;
            this.button28.UseVisualStyleBackColor = false;
            //
            // button29
            //
            this.button29.BackColor = System.Drawing.Color.White;
            this.button29.Location = new System.Drawing.Point(211, 42);
            this.button29.Name = "button29";
            this.button29.Size = new System.Drawing.Size(21, 113);
            this.button29.TabIndex = 15;
            this.button29.UseVisualStyleBackColor = false;
            //
            // button26
            //
            this.button26.BackColor = System.Drawing.Color.Black;
            this.button26.Location = new System.Drawing.Point(181, 22);
            this.button26.Name = "button26";
            this.button26.Size = new System.Drawing.Size(17, 87);
            this.button26.TabIndex = 14;
            this.button26.UseVisualStyleBackColor = false;
            //
            // button27
            //
            this.button27.BackColor = System.Drawing.Color.White;
            this.button27.Location = new System.Drawing.Point(190, 42);
            this.button27.Name = "button27";
            this.button27.Size = new System.Drawing.Size(21, 113);
            this.button27.TabIndex = 13;
            this.button27.UseVisualStyleBackColor = false;
            //
            // button25
            //
            this.button25.BackColor = System.Drawing.Color.White;
            this.button25.Location = new System.Drawing.Point(169, 42);
            this.button25.Name = "button25";
            this.button25.Size = new System.Drawing.Size(21, 113);
            this.button25.TabIndex = 12;
            this.button25.UseVisualStyleBackColor = false;
            //
            // button47
            //
            this.button47.BackColor = System.Drawing.Color.Black;
            this.button47.Location = new System.Drawing.Point(439, 22);
            this.button47.Name = "button47";
            this.button47.Size = new System.Drawing.Size(17, 87);
            this.button47.TabIndex = 35;
            this.button47.UseVisualStyleBackColor = false;
            //
            // button48
            //
            this.button48.BackColor = System.Drawing.Color.White;
            this.button48.Location = new System.Drawing.Point(448, 42);
            this.button48.Name = "button48";
            this.button48.Size = new System.Drawing.Size(21, 113);
            this.button48.TabIndex = 34;
            this.button48.UseVisualStyleBackColor = false;
            //
            // button45
            //
            this.button45.BackColor = System.Drawing.Color.Black;
            this.button45.Location = new System.Drawing.Point(418, 22);
            this.button45.Name = "button45";
            this.button45.Size = new System.Drawing.Size(17, 87);
            this.button45.TabIndex = 33;
            this.button45.UseVisualStyleBackColor = false;
            //
            // button46
            //
            this.button46.BackColor = System.Drawing.Color.White;
            this.button46.Location = new System.Drawing.Point(427, 42);
            this.button46.Name = "button46";
            this.button46.Size = new System.Drawing.Size(21, 113);
            this.button46.TabIndex = 32;
            this.button46.UseVisualStyleBackColor = false;
            //
            // button43
            //
            this.button43.BackColor = System.Drawing.Color.Black;
            this.button43.Location = new System.Drawing.Point(398, 22);
            this.button43.Name = "button43";
            this.button43.Size = new System.Drawing.Size(17, 87);
            this.button43.TabIndex = 31;
            this.button43.UseVisualStyleBackColor = false;
            //
            // button44
            //
            this.button44.BackColor = System.Drawing.Color.White;
            this.button44.Location = new System.Drawing.Point(407, 42);
            this.button44.Name = "button44";
            this.button44.Size = new System.Drawing.Size(21, 113);
            this.button44.TabIndex = 30;
            this.button44.UseVisualStyleBackColor = false;
            //
            // button42
            //
            this.button42.BackColor = System.Drawing.Color.White;
            this.button42.Location = new System.Drawing.Point(386, 42);
            this.button42.Name = "button42";
            this.button42.Size = new System.Drawing.Size(21, 113);
            this.button42.TabIndex = 29;
            this.button42.UseVisualStyleBackColor = false;
            //
            // button40
            //
            this.button40.BackColor = System.Drawing.Color.Black;
            this.button40.Location = new System.Drawing.Point(356, 22);
            this.button40.Name = "button40";
            this.button40.Size = new System.Drawing.Size(17, 87);
            this.button40.TabIndex = 28;
            this.button40.UseVisualStyleBackColor = false;
            //
            // button41
            //
            this.button41.BackColor = System.Drawing.Color.White;
            this.button41.Location = new System.Drawing.Point(365, 42);
            this.button41.Name = "button41";
            this.button41.Size = new System.Drawing.Size(21, 113);
            this.button41.TabIndex = 27;
            this.button41.UseVisualStyleBackColor = false;
            //
            // button38
            //
            this.button38.BackColor = System.Drawing.Color.Black;
            this.button38.Location = new System.Drawing.Point(335, 22);
            this.button38.Name = "button38";
            this.button38.Size = new System.Drawing.Size(17, 87);
            this.button38.TabIndex = 26;
            this.button38.UseVisualStyleBackColor = false;
            //
            // button39
            //
            this.button39.BackColor = System.Drawing.Color.White;
            this.button39.Location = new System.Drawing.Point(344, 42);
            this.button39.Name = "button39";
            this.button39.Size = new System.Drawing.Size(21, 113);
            this.button39.TabIndex = 25;
            this.button39.UseVisualStyleBackColor = false;
            //
            // button37
            //
            this.button37.BackColor = System.Drawing.Color.White;
            this.button37.Location = new System.Drawing.Point(323, 42);
            this.button37.Name = "button37";
            this.button37.Size = new System.Drawing.Size(21, 113);
            this.button37.TabIndex = 24;
            this.button37.UseVisualStyleBackColor = false;
            //
            // button59
            //
            this.button59.BackColor = System.Drawing.Color.Black;
            this.button59.Location = new System.Drawing.Point(594, 22);
            this.button59.Name = "button59";
            this.button59.Size = new System.Drawing.Size(17, 87);
            this.button59.TabIndex = 47;
            this.button59.UseVisualStyleBackColor = false;
            //
            // button60
            //
            this.button60.BackColor = System.Drawing.Color.White;
            this.button60.Location = new System.Drawing.Point(603, 42);
            this.button60.Name = "button60";
            this.button60.Size = new System.Drawing.Size(21, 113);
            this.button60.TabIndex = 46;
            this.button60.UseVisualStyleBackColor = false;
            //
            // button57
            //
            this.button57.BackColor = System.Drawing.Color.Black;
            this.button57.Location = new System.Drawing.Point(573, 22);
            this.button57.Name = "button57";
            this.button57.Size = new System.Drawing.Size(17, 87);
            this.button57.TabIndex = 45;
            this.button57.UseVisualStyleBackColor = false;
            //
            // button58
            //
            this.button58.BackColor = System.Drawing.Color.White;
            this.button58.Location = new System.Drawing.Point(582, 42);
            this.button58.Name = "button58";
            this.button58.Size = new System.Drawing.Size(21, 113);
            this.button58.TabIndex = 44;
            this.button58.UseVisualStyleBackColor = false;
            //
            // button55
            //
            this.button55.BackColor = System.Drawing.Color.Black;
            this.button55.Location = new System.Drawing.Point(553, 22);
            this.button55.Name = "button55";
            this.button55.Size = new System.Drawing.Size(17, 87);
            this.button55.TabIndex = 43;
            this.button55.UseVisualStyleBackColor = false;
            //
            // button56
            //
            this.button56.BackColor = System.Drawing.Color.White;
            this.button56.Location = new System.Drawing.Point(562, 42);
            this.button56.Name = "button56";
            this.button56.Size = new System.Drawing.Size(21, 113);
            this.button56.TabIndex = 42;
            this.button56.UseVisualStyleBackColor = false;
            //
            // button54
            //
            this.button54.BackColor = System.Drawing.Color.White;
            this.button54.Location = new System.Drawing.Point(541, 42);
            this.button54.Name = "button54";
            this.button54.Size = new System.Drawing.Size(21, 113);
            this.button54.TabIndex = 41;
            this.button54.UseVisualStyleBackColor = false;
            //
            // button52
            //
            this.button52.BackColor = System.Drawing.Color.Black;
            this.button52.Location = new System.Drawing.Point(511, 22);
            this.button52.Name = "button52";
            this.button52.Size = new System.Drawing.Size(17, 87);
            this.button52.TabIndex = 40;
            this.button52.UseVisualStyleBackColor = false;
            //
            // button53
            //
            this.button53.BackColor = System.Drawing.Color.White;
            this.button53.Location = new System.Drawing.Point(520, 42);
            this.button53.Name = "button53";
            this.button53.Size = new System.Drawing.Size(21, 113);
            this.button53.TabIndex = 39;
            this.button53.UseVisualStyleBackColor = false;
            //
            // button50
            //
            this.button50.BackColor = System.Drawing.Color.Black;
            this.button50.Location = new System.Drawing.Point(490, 22);
            this.button50.Name = "button50";
            this.button50.Size = new System.Drawing.Size(17, 87);
            this.button50.TabIndex = 38;
            this.button50.UseVisualStyleBackColor = false;
            //
            // button51
            //
            this.button51.BackColor = System.Drawing.Color.White;
            this.button51.Location = new System.Drawing.Point(499, 42);
            this.button51.Name = "button51";
            this.button51.Size = new System.Drawing.Size(21, 113);
            this.button51.TabIndex = 37;
            this.button51.UseVisualStyleBackColor = false;
            //
            // button49
            //
            this.button49.BackColor = System.Drawing.Color.White;
            this.button49.Location = new System.Drawing.Point(478, 42);
            this.button49.Name = "button49";
            this.button49.Size = new System.Drawing.Size(21, 113);
            this.button49.TabIndex = 36;
            this.button49.UseVisualStyleBackColor = false;
            //
            // Form1
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(652, 180);
            this.Controls.Add(this.button59);
            this.Controls.Add(this.button60);
            this.Controls.Add(this.button57);
            this.Controls.Add(this.button58);
            this.Controls.Add(this.button55);
            this.Controls.Add(this.button56);
            this.Controls.Add(this.button54);
            this.Controls.Add(this.button52);
            this.Controls.Add(this.button53);
            this.Controls.Add(this.button50);
            this.Controls.Add(this.button51);
            this.Controls.Add(this.button49);
            this.Controls.Add(this.button47);
            this.Controls.Add(this.button48);
            this.Controls.Add(this.button45);
            this.Controls.Add(this.button46);
            this.Controls.Add(this.button43);
            this.Controls.Add(this.button44);
            this.Controls.Add(this.button42);
            this.Controls.Add(this.button40);
            this.Controls.Add(this.button41);
            this.Controls.Add(this.button38);
            this.Controls.Add(this.button39);
            this.Controls.Add(this.button37);
            this.Controls.Add(this.button35);
            this.Controls.Add(this.button36);
            this.Controls.Add(this.button33);
            this.Controls.Add(this.button34);
            this.Controls.Add(this.button31);
            this.Controls.Add(this.button32);
            this.Controls.Add(this.button30);
            this.Controls.Add(this.button28);
            this.Controls.Add(this.button29);
            this.Controls.Add(this.button26);
            this.Controls.Add(this.button27);
            this.Controls.Add(this.button25);
            this.Controls.Add(this.button23);
            this.Controls.Add(this.button24);
            this.Controls.Add(this.button21);
            this.Controls.Add(this.button22);
            this.Controls.Add(this.button19);
            this.Controls.Add(this.button20);
            this.Controls.Add(this.button18);
            this.Controls.Add(this.button16);
            this.Controls.Add(this.button17);
            this.Controls.Add(this.button14);
            this.Controls.Add(this.button15);
            this.Controls.Add(this.button13);
            this.Name = "Form1";
            this.Text = "Piano";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.Button button13;
        private System.Windows.Forms.Button button15;
        private System.Windows.Forms.Button button14;
        private System.Windows.Forms.Button button16;
        private System.Windows.Forms.Button button17;
        private System.Windows.Forms.Button button18;
        private System.Windows.Forms.Button button19;
        private System.Windows.Forms.Button button20;
        private System.Windows.Forms.Button button21;
        private System.Windows.Forms.Button button22;
        private System.Windows.Forms.Button button23;
        private System.Windows.Forms.Button button24;
        private System.Windows.Forms.Button button35;
        private System.Windows.Forms.Button button36;
        private System.Windows.Forms.Button button33;
        private System.Windows.Forms.Button button34;
        private System.Windows.Forms.Button button31;
        private System.Windows.Forms.Button button32;
        private System.Windows.Forms.Button button30;
        private System.Windows.Forms.Button button28;
        private System.Windows.Forms.Button button29;
        private System.Windows.Forms.Button button26;
        private System.Windows.Forms.Button button27;
        private System.Windows.Forms.Button button25;
        private System.Windows.Forms.Button button47;
        private System.Windows.Forms.Button button48;
        private System.Windows.Forms.Button button45;
        private System.Windows.Forms.Button button46;
        private System.Windows.Forms.Button button43;
        private System.Windows.Forms.Button button44;
        private System.Windows.Forms.Button button42;
        private System.Windows.Forms.Button button40;
        private System.Windows.Forms.Button button41;
        private System.Windows.Forms.Button button38;
        private System.Windows.Forms.Button button39;
        private System.Windows.Forms.Button button37;
        private System.Windows.Forms.Button button59;
        private System.Windows.Forms.Button button60;
        private System.Windows.Forms.Button button57;
        private System.Windows.Forms.Button button58;
        private System.Windows.Forms.Button button55;
        private System.Windows.Forms.Button button56;
        private System.Windows.Forms.Button button54;
        private System.Windows.Forms.Button button52;
        private System.Windows.Forms.Button button53;
        private System.Windows.Forms.Button button50;
        private System.Windows.Forms.Button button51;
        private System.Windows.Forms.Button button49;
    }
}