Erste Umsetzung des Übungsformates
Details zur Implementierung und wie es (nicht) funktioniert (16.07.2019)
Förderjahr 2018 / Project Call #13 / ProjektID: 3157 / Projekt: OSME - Open Sheet Music Education

Dieser Post ist ein Folgeartikel von: https://www.netidee.at/osme-open-sheet-music-education/von-der-idee-zur-skizze

Framework

Das erste, was zu überprüfen war, war ein gutes Framework für den Anfang. Wir haben uns für Javascript/Typescript entschieden, da Open Sheet Music Display auch in dieser Sprache umgesetzt wird. Um eine Demo einfach einzurichten, wählen wir React, eine Open Source Frontend-Bibliothek, die von Facebook entwickelt wird. Vor einiger Zeit wollten wir auch eine React-Demo für OSMD haben, also stand das sowieso auf unserer Wunschliste.

Das UI wird mit Material Design und Bootstrap gemacht, welche ebenfalls Open-Source-Projekte sind. Damit können wir uns voll auf den Code konzentrieren.

OSME Explorer

Der Code, den wir jetzt aufgrund der bisherigen Recherche und Diskussionen schreiben wollen, ist ein Viewer, der die Struktur unserer Übungen anzeigt und mit dem der Benutzer sie auch erkunden kann. Es wird eine sehr einfache UI-Layer sein, die nur eine Listenansicht mit den Übungseinträgen und Prozentsätzen des Erfolgs des Users anzeigt.

Wir zeigen auch, ob auf Basis des Ergebnisses bereits auch eine neue Übung freigeschalten wurde.

OSME Interface

Der Code dahinter

Wir beginnen damit, unseren Code so einzustellen, dass er so ist, wie wir es in den früheren Blog-Posts diskutiert haben:

   const ons = new PracticeContainer("one-note-samba.mxl");
   ons.Title = "One Note Samba";
   ons.Description = "The very beginning"
   ons.AddGoal(goal);
   ons.AddResult(passedResult);

Wie wir sehen, hat unser Container einen Konstruktor, der die MusicXML-Datei aufruft und danach können wir einen Titel, eine Beschreibung und ein Ziel setzen.

Bei der Umsetzung haben wir festgestellt, dass wir unser Ziel mit etwas vergleichen müssen. Aus diesem Grund haben wir eine weitere Klasse, die (im Moment) die gleichen Methoden wie das Ziel hat und als Matcher verwendet werden kann.

Die neue PracticeContainer-Klasse sieht so aus:

export class PracticeContainer {
   public Goals: Goal[] = [];
   public Results: Result[] = [];
   public MusicXml: string;
   public Title: string = "";
   public Description: string = "";
}

Wir haben die zu erreichenden Ziele, eine Beschreibung und die Ergebnisse für genau diese Übung. Wie überprüfen wir also, ob wir unsere Übung tatsächlich bestanden haben?

public Passed() {
   if(this.Results.length === 0 || this.Goals.length === 0) {
       return false;
   }
   const bestResult: Result = this.SortedResults[this.SortedResults.length-1];
   return this.Goals.map(g => g.Passed(bestResult)).every(b => b === true);
}

Diese Methode überprüft alle unsere Ziele mit einer Methode goal.Passed() für das letzte Element in unserer sortierten Ergebnisliste. Was bedeutet das? Wir sortieren unsere Ergebnisse nach der Logik, dass das beste Ergebnis das letzte in der Liste ist. Der beste Run wird dann als Ziel gesetzt, welches sich dann selbst verifiziert und zurückgegeben wird. Die Funktion every() auf dem resultierenden logischen Array überprüft jedes Element in der Liste und gibt zurück, wenn alle zutreffend sind.

Lassen Sie uns einen genaueren Blick auf unsere Zielklasse werfen:

export class Goal {
   Speed: SpeedConstraint = new SpeedConstraint();   
   PitchAccuracy: PitchAccuracy = new PitchAccuracy();
   TimeAccuracy: TimeAccuracy = new TimeAccuracy();
   ChordSimultaneity: ChordSimultaneity = new ChordSimultaneity([1, 2, 3, 4]);
   CustomMatch: IConstraint | undefined;
   Mandatory: Boolean = false;
  
   public Passed(practiceResult: Result) {
       const customPass: Boolean = this.CustomMatch ? this.CustomMatch.Passed(practiceResult.CustomMatch) : true;
       const speedPass: Boolean = this.Speed.Passed(practiceResult.Speed);
       const pitchass: Boolean = this.PitchAccuracy.Passed(practiceResult.PitchAccuracy);
       const timePass: Boolean = this.TimeAccuracy.Passed(practiceResult.TimeAccuracy);
       const chordsPass: Boolean = this.ChordSimultaneity.Passed(practiceResult.ChordSimultaneity);
       return customPass && speedPass && pitchass && timePass && chordsPass;
   }
}

Ein Ziel wird durch verschiedene “constraints” definiert. Diese “constraints” sind Implementierungen dem IConstraint-Interface. Es erfordert Methoden zur Überprüfung ob ein “constraint” von einer anderen überschritten wird. Das bedeutet, dass wir das Ergebnis der Genauigkeit der Tonlage leicht mit dem Ziel vergleichen können, ohne uns um die Implementierung (die derzeit sowieso untersetzt ist, siehe ChordSimultaneity) zu kümmern.

Wie wir sehen, ist ein Ziel erreicht, wenn alle “constraints” im Vergleich zum Ergebnis bestanden werden.

Wo als nächstes?

Der nächste Schritt ist es zu überprüfen ob die Implementierung unseren Anforderungen entspricht und ob diese leicht erweiterbar mit Hilfe des “constraint-interface” ist:

import { IConstraint } from "./IConstraint";

export class SpeedConstraint implements IConstraint{
   public ConstraintValue: number = 90;

   Passed(otherConstraint: IConstraint): Boolean {
       return otherConstraint.ConstraintValue >= this.ConstraintValue;
   }

   IsBetterThan(otherConstraint: IConstraint): Boolean {
       return this.ConstraintValue >= otherConstraint.ConstraintValue;
   }
  
   IsCompatible(otherConstraint: IConstraint): Boolean {
       throw new Error("Method not implemented.");
   }
}

Wir werden auch die richtigen Muster für diese “constraints” implementieren. Was noch fehlt, ist nun die Abhängigkeit, die löst, ob eine Übung für eine andere nötig ist. Dies wird auch in das UI des OSME-Explorers aufgenommen, um zu zeigen, wie die Datei gemacht wird.

Donita-Anne Pascual

Profile picture for user dontneeda
Hallo! Mein mein Name ist Donita und ich bin Studentin an der Bournemouth University, Großbritannien. Als Teil des PhonicScore Teams (der Firma hinter OSME und OSMD), bin ich im Bereich Marketing tätig.
CAPTCHA
Diese Frage dient der Überprüfung, ob Sie ein menschlicher Besucher sind und um automatisierten SPAM zu verhindern.

    Weitere Blogbeiträge

    Datenschutzinformation
    Der datenschutzrechtliche Verantwortliche (Internet Privatstiftung Austria - Internet Foundation Austria, Österreich) würde gerne mit folgenden Diensten Ihre personenbezogenen Daten verarbeiten. Zur Personalisierung können Technologien wie Cookies, LocalStorage usw. verwendet werden. Dies ist für die Nutzung der Website nicht notwendig, ermöglicht aber eine noch engere Interaktion mit Ihnen. Falls gewünscht, können Sie Ihre Einwilligung jederzeit via unserer Datenschutzerklärung anpassen oder widerrufen.