Einen Grafik Equilizer mit Power BI bauen

In diesem Artikel zeige ich, wie Benjamin Kettner und ich für die BI Power Hour auf der SQL Konferenz 2018 in Darmstadt eine Grafik Equilizer mit Power BI umgesetzt haben. Bei der BI Power Hour geht es darum nicht ganz ernst gemeinte Vorträge zu präsentieren die trotzdem einen gewissen technischen Anspruch haben. Es wird etwas umgesetzt was technisch interessant aber auch in sich möglicherweise sinnfrei ist, ganz unter dem Motto „Spass mit Technik“.

Da sowohl Ben als auch ich große Metal-Fans sind ist in verschiedenen Gesprächen die Idee entstanden „Lass uns doch mal was zum Thema Metal machen mit Power BI“. Und so war schnell die Idee geboren eine Metal Show auf die Füße zu stellen. Und wenn schon Metal, dann natürlich auch Power Metal :).

Unser Vortrag war in zwei Teile aufgsplittet: zum einen haben wir (bzw. hauptsächlich Ben) einen Webservice erstellt der die Lyrics eines Metal Songs einliest und dann für jedes Wort zurückgibt wie oft es im Text auftaucht. Vielleicht gibt es zu diesem Thema noch mal einen eigenen Artikel bei dem erklärt wird was wir genau gemacht haben. Beim zweiten Teil hatte ich die Idee einen Grafik-Equilizer, wie man ihn beispielsweise von Winamp kennt, umzusetzen. Die Idee war einfach eine Frequenzanalyse zu machen. Über diese bekommt man pro Zeitslot und pro Frequenz einen Wert, d.h. also pro Zeitslot wird ein Feld erzeugt dass wie folgt aussieht:

Wenn man das dann in einem Balkendiagramm darstellt bekommt man die folgende Darstellung:

Stellt man sich nun vor, dass sich die Werte ständig ändern und man das ganze ständig aufzeichnet, so haben wir schon einen Grafik Equilizer der den Ausschlag der unterschiedlichen Frequenzen darstellt.

Um dies mit Power BI zu realisieren haben wir die Technik der „Streaming Datasets“ verwendet. Die Vorgehensweise ist hierbei die folgende: Zunächst muss man im Power BI Service ein Streaming Dataset anlegen. Dann kopiert man die URL, die Power BI für das Streaming Dataset erzeugt hat, um dort per HTTP-POST JSon Dateien hinzuschicken die das Format haben müssen das der Power BI Service erwartet. Bei der Realisierung für die BI Power Hour haben wir uns für eine Implementierung des Streaming Clients als Java Script Code innerhalb einer Webseite entschieden.

Um das Steaming Dataset in PowerShell anzulegen muss man wie folgt vorgehen:

1.) Zunächst muss man im Arbeitsbereich in dem man das Streaming Dataset anlegen möchte auf Erstellen und dann auf Streamingdataset klicken.

 

2.) Für diese Implementierung des Equilizers nutzen wir ein Streamingset über die API. Daher muss hier API ausgewählt werden und dann auf Weiter klicken.

 

3.) Nun müssen wir unser Streamindataset definieren. Im Beispiel geben wir ihm den Namen PowerBIEquilizer und definieren die beiden Felder Frequenz und Wert. Obwohl die Werte im Feld Frequenz Zahlen sind müssen wir hier doch angeben dass es sich um ein Textfeld handelt damit wir das Feld an die Achse des Balkendiagramms binden können. Das zweite Feld heißt Wert und ist einfach ein Zahlenfeld. Nachdem das soweit konfiguriert worden ist müssen wir nun noch auf den Butten Erstellen klicken.

4.) Nachdem das Streamindataset erfolgreich erzeugt wurde können wird nun die folgende Website angezeigt. Wichtig ist die Push-URL, die wir im Code gleich benötigen um die Daten an den Power BI Service zu schicken.

Damit sind wir auf der Power BI Seite fertig und müssen uns nun um den Streaming Client kümmern, der die Daten an Power BI schickt. In diesem Fall habe ich mich dafür entschieden eine Website zu bauen in der ein JavaScript läuft das die Daten in regelmäßigen Abständen an den Service schickt und somit einen Datastream erzeugt. Als erstes schauen wir uns einmal den Code der Website an.

<body bgcolor="#000000">
  <!--FGE: here you have to insert the MP3 Song that has to be streamed-->
  <audio id="audioElement" src="./audio/Fear Of The Dark.mp3"></audio>

  <div>
   <button onclick="document.getElementById('audioElement').play()">Play 'Fear of the Dark'</button>
   <button onclick="document.getElementById('audioElement').pause()">Pause 'Fear of the Dark'</button>
   <button onclick="document.getElementById('audioElement').volume+=0.1">Increase Volume</button>
   <button onclick="document.getElementById('audioElement').volume-=0.1">Decrease Volume</button>
  </div>
  <br/>
  <script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
  <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
  <script src="app.js"></script>
  <img src="Bilder/1000x1000.jpg" width="800" height="800" class="center"/>
</body>

In Zeile 3 wird das Audio Element audioElement erzeugt, das abgespielt und analysiert wird. Im Demo haben wir uns (neben der Tatsache das Fear of The Dark ein sehr geiler Song ist) für „Fear of the Dark“ von Iron Maiden entschieden da er laute und leise Passagen hat und das dann in Power BI auch ziemlich gut aussieht. Natürlich funktioniert das auch mit jeder anderen MP3 Datei, die muss nur an die richtige Stelle kopiert werden und die Zeile 3 muss angepasst werden.

In Zeile 6-9 werden die Buttons definiert über die man die Musikwiedergabe steuern kann. Im Rest des Codes werden noch die Java Script Scripte geladen. Hier ist besonders das Script app.js wichtig, weil in diesem Script der Code enthalten ist den wir uns als nächstes anschauen werden. Die fertige Website sieht wie unten angezeigt aus.

Zum Schluss schauen wir uns noch den Code in der Java Script Datei app.js an.

Oben in der Datei werden erst mal die Objekte erzeugt die wir für die Verarbeitung und Analyse der MP3 Datei benötigen. Wichtig hier ist, dass sich alles auf das Element audioElement bezieht, dass wir in der HTML Datei definiert haben. Zunächst erzeigen wir einen Audio Context über window.AudioContext oder window.webkitAudioContext. Die Schreibweise die wir gewählt haben ist die browserunabhängige Schreibweise, d.h. der Audio Context wird in jedem Browser erzeugt. Als nächstes wird ein Audioelement erzeugt das aus der HTML-Seite ausgelesen wird und dann über createMediaElementSource eine Audio Quelle zu erzeugen. Zu guter Letzt wird dann noch über createAnalyzer ein Analyzer erstellt der dann die entsprechenden Zahlen für die Frequenzen liefert.

// FGE: Create AudioContext
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// FGE: Create AudioElement and load from HTML
var audioElement = document.getElementById('audioElement');
// FGE: Create Audio Source from Audio Context
var audioSrc = audioCtx.createMediaElementSource(audioElement);
// FGE: Create Analyzer from Audio Context
var analyser = audioCtx.createAnalyser();

als nächstes binden wir den Analyzer und das Ziel der Audioausgabe an die Audio-Quelle. Danach erzeugen wir ein Array dass die Frequenzwerte aufnehmen soll.

// FGE: Bind analyser to the media element source.
audioSrc.connect(analyser);
audioSrc.connect(audioCtx.destination);
// FGE: Array that will hold the frequencyData
var frequencyData=newUint8Array(200);

Meine Experimente mit dem Datenstrom haben ergeben, dass Power BI auf diese Art das Data Streamings nicht mit besonders vielen Daten klar kommt. Wenn wir den so erzeugten Datenstrom direkt an Power BI schicken gibt es vom Service die Meldung zurück dass man bitte die Anzahl der gesendeten Daten reduzieren  möge. Ich habe das auf zwei Arten gemacht. Zum einen fasse ich die Werte die an den Service gesendet werden in Buckets zusammen, zum anderen habe ich eine Wartefunktion eingebaut, mit der man eine bestimmte Zeit warten kann bevor es weitergeht. Als gute Werte haben sich eine Bucket-Size von 20 Elemente pro Bucket und eine Wartezeit von einer halben Sekunde herausgestellt. Die folgenden Zeilen beschreiben wie viele Elemente in einem Bucket sind und wie häufig die entsprechende Aggregationsschleife durchlaufen werden müssen.

// FGE: To reduce the data that will be sent to PowerBI we create
// buckets. Each bucket stands for a number of fequency values
// that will be summed up in the bucket
var bucketsize=20;
// FGE: Number of steps is calculated - it is defining how often the
// sum should be aggregated for the whole dataset
var number_of_steps=200/bucketsize;

Die eigentliche Arbeit erledigt die folgende Schleife. Zunächst werden die Frequenzdaten vom Analyzer geholt und in das Array geschrieben. Dann wird eine JSON-Datensatz erzeugt und es werden die Daten innerhalb der Buckets aufaggregiert. Dann werden die Summen jeweils in den JSON-Datensatz geschrieben. Zum Schluss wird der JSON-Datensatz vervollständigt (letztes Komma löschen und eckige Klammer zumachen). Der JSON Datensatz wird dann in die Debug-Konsole des Browsers geschrieben, damit man sehen kann dass Streaming Data erzeugt werden.

    // FGE: Continuously loop and create JSON with frequency data.
    function streamData() {

        // FGE: Copy frequency data to frequencyData array.
        analyser.getByteFrequencyData(frequencyData);

        // FGE: Create a JSON file for streaming from the frequency data
        powerbi_dataset = "["

        // FGE: This loop aggregates the values in the bucket 
        for (var j = 0; j < number_of_steps; j++)
        {
            var start_number = j * bucketsize;
            var end_number = (j+1) * bucketsize;

            var sum = 0;
            for (var s = start_number; s < end_number; s++) {
                sum += frequencyData[s]; 
            }

            var avg = sum / bucketsize;
            powerbi_dataset = powerbi_dataset + "{\"Frequenz\" :\"" + j + "\", \"Wert\" :" + sum + "},";     
        }

        // FGE: Kill the last comma
        powerbi_dataset = powerbi_dataset.slice(0, -1);
        powerbi_dataset = powerbi_dataset + "]";

        // FGE: Log the Json File to the debug console
        console.log(powerbi_dataset);

        // FGE: Post Data to Power BI Service
        jQuery.ajax({
            type: "POST",
            url: "https://<power_bi_url>",
            cache: false,
            data: powerbi_dataset,
            dataType: "json",
            contentType: "json"
        });

        // FGE: Wait for half a second
        sleep(500); 

       // FGE: Used to update browser window 
       requestAnimationFrame(streamData);
    }

Das gesamte Projekt (natürlich ohne Audio-Files oder Bilder) habe ich auf GitHub hochgeladen und man kann es hier anschauen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

%d Bloggern gefällt das: