Benutzer-Werkzeuge

Webseiten-Werkzeuge


start:themen:synthcommander

SynthCommander: Control Synthesizers via WebMidi

Nach langer Zeit greife ich (nun als Rentner) die Musikprogrammierung wieder auf. Da ich zuletzt beruflich mit dem Web-Framework Angular einige Single-Page Frontend- Anwendungen entwickelt habe, verwende ich es nun, um damit Synthesizer z.B. der Korg Volca Serie und dem NTS1 digital kit per MIDI einzurichten. Das bedeutet eine wirkliche „serverless“ Webanwendung, die tatsächlich nur zur Auslieferung an den Browser einen Webserver braucht. Der Browsercache erlaubt dann Updates der App quasi nebenbei.

Dieser Artikel vermittelt mit Einrichtung und allgemeinen Aufbau von Angular-Anwendungen wichtige Grundlagen. Wer sich damit bereits auskennt, kann hier weiterlesen: Aufbau der SynthCommander App

Einrichtung

Die Angular CLI (aktuell in der Version 10) ist nicht nur ein Web-Framework, sondern eine komplette Continous Integration Plattform. Das bedeutet, dass der gesamte Entwicklungsprozess von Ein/Auschecken von einem Quellcode-Repository wie Git über Bearbeitung, Codeanalyse („lint“), Übersetzung bis zur Ausbreitung automatisierbar ist. Für neue Projekte werden „Anwendungsgerüste“ (scaffolds) erstellt, die man schrittweise zur fertigen Anwendung erweitert. Bei jedem Abspeichern von Quellcode wird automatisch der gesamte oben beschriebene Ablauf durchlaufen und die dabei erstellte Webanwendung automatisch aktualisiert - oder mit Fehlermeldungen abgebrochen.

NodeJS und NPM als Fundament

Wie die meisten Frontend- Entwicklungswerkzeuge (und inzwischen auch Tools zur Backend-Entwicklung) basiert Angular auf NodeJS und seiner Javacript Quellcode- Bibliothek NPM. Auf der verlinkten Seite kann es für die gängigsten Betriebssysteme direkt herunter geladen werden, wobei NPM meist schon mit NodeJS ausgeliefert wird. NodeJS wird häufig schon von Linux- Distributionen ausgebreitet, aber leider meist in schon älteren Versionen, aber NodeJS wird sehr häufig aktualisiert.

Für Debian/Ubuntu- Distributionen sollte man etwa eine eigene Repository-Eintragung einrichten bevor man NodeJS einrichtet:

# NodeJS Repository Eintrag runterladen
$ curl -sL https://deb.nodesource.com/setup_12.x -o nodesource_setup.sh
# die Versionsnummer oben mit einem Texteditor wie "nano" oder "vim" aktualisieren
$ nano nodesource_setup.sh
# das Repository einrichten
$ sudo bash nodesource_setup.sh
# die Debian Repositories aktualisieren
$ sudo apt update
# die gewünschte Version von NodeJS installieren
$ sudo apt install nodejs
# die Installation prüfen
$ node -v # sollte dann die Version anzeigen
$ npm -v # sollte dann die NPM Version anzeigen
# manche NodeJS Bibliotheken sind "hybrid", d.h. enthalten nicht nur Javascript, sondern auch nativen C/C++ Quellcode
# deshalb sollte sichergestellt sein, dass auch eine C/C++ Entwicklungsumgebung installiert ist:
$ sudo apt install build-essential
# zur Verwaltung des eigenen Quellcodes sollte auch noch Git installiert werden
$ sudo apt install git

Unter Windows verwendet der NodeJS Installer inzwischen das Tool Chocolatey zur Installation, um für die Übersetzung nativer / hybrider Bibliotheksanteile die richtigen Entwicklungstools (auch etwa einen Python-Interpreter) mitzuinstallieren. Das bedeutet zwar weniger Tipperei als bei Linux, aber ist wegen der vielen nötigen Installationen unter Windows aber sehr langwierig

Angular & Co

Wenn Node und NPM erst mal laufen, hat man den Grundstein für die das Meiste der aktuellen Web-Frontend-Entwicklung gelegt und mit dem Express-Framework kann Node auch für die Server-Seite recht universell eingesetzt werden. Express vereinfacht aber vor allem klassische, Server-zentrische (Multi-Page) Webanwendungen sehr, kann bei Single-Page- Anwendungen aber auch die hier benötigten Web-Services leicht bereitstellen. Bei Single-Page-Anwendungen, wie man sie etwa mit Angular erstellt, liefert der Webserver nicht für jeden Anwendungsfall HTML-Seiten, CSS- und Javascript- Code aus, sondern liefert nur einmal zum Start der Anwendung den gesamten Code als Javascript-Paket-Bündel (bundle oder „chunks“) aus. Da bei aktuellen Browsern Seiten mittels Javascript und dessen DOM-Modell effektiver und schneller aufgebaut werden kann, liefern typische Single-Page Frameworks wie Angular, ReactJS und Vue.js kein CSS oder HTML mehr an den Browser. Das bedeutet mehrere Übersetzungstools - und -Schritte, die von einem (meist) integrierten Tool namens webpack im Rahmen der automatischen Übersetzung erledigt werden.

Ein Problem aller Browser (vor allem älterer) ist die unterschiedlich vollständige Implementierung von Standards wie ECMAScript (Javascript), HTML5 und CSS3, wie man es für moderne Webseiten auf den unterschiedlichsten Geräten erwartet. Wenn man schon beim Übersetzen ist, können geeignete Übersetzer auch dies Problem - in Verbindung mit „shim“ (Unterlegscheibe) genannten Bibliotheken oft lösen. Als Angular- Entwickler verwendet man deshalb statt irgendeiner Javascript-Version gleich das statisch typisierte TypeScript, statt CSS einen SCSS-Compiler und eine HTML5 aufwärtskompatible Template-Language für den Aufbau der Webseiten.

Diese Übersetzer und vor- und nachgeschaltete Codeanalyse-Tools (wie z.B. TSLint) stellen sicher, dass nur formal nach festlegbaren Konventionen „sauberer“ Quellcode übersetzt, ausgeführt und letztlich ins Repository zurück geschrieben werden kann. Der Übersetzung können auch automatisierte Testläufe einzelner Module (Unit-Tests, Jasmine Framework), Integrationstests oder gar integrierte E2E- Anwendungstests mittels einer Chromium-Browser-Emulation folgen. Über Metrik-Tools kann dabei auch die Performance der Anwendung getestet werden.

All das wird bei der Installation der Angular CLI mitinstalliert, beim Erstellen neuer Anwendungen kann aber noch ausgewählt werden, welche Teile dieses Riesenpakets in ein neues Projektverzeichnis installiert werden sollen. Zuerst muss man natürlich mit NPM Angular selbst installieren. Um mit dem Befehl „ng“ Angular systemweit nutzen zu können, sollte bei NPM die „global“-Option (-g) genutzt werden:

$ sudo npm install -g @angular/cli

Danach sollte der Befehl „ng“ eine Liste möglicher Befehle ausgeben. Wird ein Befehlswort gefolgt von „–help“ eingegeben, wird eine Detailbeschreibung dieses Befehls angezeigt:

$ ng new --help

Für ein ganz simples Anwendungsgerüst kann man den folgenden Befehl eingeben:

$ ng new HelloAngular --style=scss --minimal --skip-tests --routing=false

Dabei wird ein Unterverzeichnis „HelloAngular“ erstellt, in welches alles für die Entwicklung angeforderte (benötigte) kopiert wird. Hier habe ich mit den Optionen „minimal“, „skip-tests“ und „routing=false“ die sonst standardmäßig installierten Testtools und das Modul zur Seitennavigation (routing) weg gelassen, aber die CLI installiert trotzdem fast stolze 293 MB Code in 27186 Dateien und Verzeichnissen auf die Festplatte:

27.186 Objekte der Gesamtgröße 223,2 MB (292,8 MB auf Festplatte)

Unter Linux geht das auch blitzschnell, unter Windows geht der Benutzer aber seiner Lieblingstätigkeit nach - dem Warten (oder wieso benutzt er/sie dies Betriebssystem immer noch ;) ). Die meisten Dateien sind allerdings im Unterverzeichnis „node_modules“ abgelegt, welches man vor einer Ausbreitung des Quellcodes unter Linux auch schnell (< 1 Sekunde) und ohne Verluste löschen kann:

$ cd HelloAngular
$ rm -r node_modules
# danach: 20 Objekte der Gesamtgröße 362,9 kB (413,7 kB auf Festplatte)
# zum neu installieren im Zielverzeichnis der Kopie einfach npm benutzen
$ npm install

Die CLI enthält natürlich auch einen Testserver, der nach jeder Quellcode-Änderung die Übersetzung veranlasst und die Anwendung dem Browser bereit stellt:

$ npm start
# oder gleichbedeutend:
$ ng serve

** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **
 93% after chunk asset optimization SourceMapDevToolPlugin es2015-polyfills.js generate S 93% after chunk asset optimization SourceMapDevToolPlugin polyfills.js generate SourceMa 93% after chunk asset optimization SourceMapDevToolPlugin es2015-polyfills.js attach Sou                                                                                        
Date: 2020-05-26T09:37:41.912Z
Hash: 12cf37d2c55d9ae4a004
Time: 5428ms
chunk {es2015-polyfills} es2015-polyfills.js, es2015-polyfills.js.map (es2015-polyfills) 285 kB [initial] [rendered]
chunk {main} main.js, main.js.map (main) 9.05 kB [initial] [rendered]
chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 236 kB [initial] [rendered]
chunk {runtime} runtime.js, runtime.js.map (runtime) 6.08 kB [entry] [rendered]
chunk {styles} styles.js, styles.js.map (styles) 16.6 kB [initial] [rendered]
chunk {vendor} vendor.js, vendor.js.map (vendor) 3.52 MB [initial] [rendered]
「wdm」: Compiled successfully.

Diesem Befehl folgen (besonders beim ersten Mal) viele Zeilen mit dem oben angezeigten Ergebnis. Mit einem modernem Browser wie Chrome oder Firefox kann man nun http://localhost:4200/ öffnen und sich die noch recht langweilige (statische) Startseite des neuen Projekts betrachten. In der Liste werden aber auch die erstellten „Chunks“ gelistet, die an den Browser geschickt werden. Dabei sind die „map“-Dateien aber nur Symboltabellen, welche dem Browser-Debugger den originalen Quellcode bereitstellen, was auch ein Debuggen von nun verpackten Typescript-, HTML- und CSS- Quellen erlaubt, die für den Browser eigentlich gar nicht mehr existieren.

Zum Erstellen von Produktions-Builds, also zur Ausbreitung über einen Webserver wie nginx oder apache verwendet man folgendes CLI Kommando:

$ ng build --aot

Das dauert erheblich länger als die „Schnellübersetzung“ mit „ng serve“, besonders bei Verwendung der „ahead of time“ (aot) Option, welche soviel wie möglich vorübersetzt und die Performance für den Browser noch verbessert. Das Ergebnis findet sich dann im „dist“ Unterverzeichnis der Anwendung:

es2015-polyfills.js
es2015-polyfills.js.map
favicon.ico
index.html
main.js
main.js.map
polyfills.js
polyfills.js.map
runtime.js
runtime.js.map
styles.js
styles.js.map
vendor.js
vendor.js.map

Neben den erwähnten „map“-Dateien gibt es also nur ein favicon.ico, einige Javascripts (js-Dateien) und eine index.html, die der Webserver ausliefert, wenn die Seite angesprochen wird:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>HelloAngula</title>
  <base href="/">
 
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
<script type="text/javascript" src="runtime.js"></script><script type="text/javascript" src="es2015-polyfills.js" nomodule></script><script type="text/javascript" src="polyfills.js"></script><script type="text/javascript" src="styles.js"></script><script type="text/javascript" src="vendor.js"></script><script type="text/javascript" src="main.js"></script></body>
</html>

Mehr HTML oder gar CSS gibt es nicht mehr! Nur die angesprochenen JS-Dateien werden noch geladen, um die Seite aufzubauen. Diese Seite sieht wohl auch für fast alle Angular-Anwendungen gleich aus - die eigentliche Anwendung verbirgt sich hinter dem Pseudo-HTML-Tag <app-root> - egal was für eine Anwendung sich dahinter später verbirgt. Im Quellcode einer Angular- Anwendung liegen meist alle anwendungsspezifischen Dateien im Unterverzeichnis „src/app“; an alle anderen Dateien dienen zu eine „übergreifenden“ Konfiguration z.B. eines Teams bzw. den meisten Anwendungen eines Kunden.

Im Verzeichnis „src“ liegt etwa die Datei „polyfills.ts“ mit den oben erwähnten „shims“, um Lücken in der Implementierung einzelner Browser zu schließen, „main.ts“ erledigt den „Bootstrap“, also den Start des Haupt-Anwendungsmoduls, das per Konvention meist durch die TypeScript-Klasse „AppModule“ dargestellt wird:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
 
import { AppComponent } from './app.component';
 
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Das Modul AppModule liegt als Datei „app.module.ts“ im erwähnten „app“ Verzeichnis neben der Datei „app.component.ts“, welche schlussendlich den Quellcode der in diesem Beispiel dargestellten Seite enthält. Angular-Anwendungen setzen sich aus „Modulen“ zusammen, die jeweils durch eine solche @NgModule- Definition beschrieben werden, die wiederum andere Typescript-Klassen als Komponenten, Direktiven und Services benennt. Angular selbst besteht auch komplett aus Modulen wie dem oben importierten „BrowserModule“. Eine Komponente dient beim Hauptmodul als „bootstrap“, wodurch die oben genannte „main.ts“ die Anwendung startet. Im Beispiel wird dann also die Komponente „AppComponent“ vom Browser dargestellt.

import { Component } from '@angular/core';
 
@Component({
  selector: 'app-root',
  template: `
    <!--The content below is only a placeholder and can be replaced.-->
    <div style="text-align:center">
      <h1>
        Welcome to {{title}}!
      </h1>
      <img width="300" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
    </div>
    <h2>Here are some links to help you start: </h2>
    <ul>
      <li>
        <h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
      </li>
      <li>
        <h2><a target="_blank" rel="noopener" href="https://angular.io/cli">CLI Documentation</a></h2>
      </li>
      <li>
        <h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
      </li>
    </ul>
  `,
  styles: []
})
export class AppComponent {
  title = 'HelloAngula';
}

Eine „Komponente“ besitzt im Gegensatz zu einer einfachen „Direktive“ einen „View“ - also darzustellenden HTML-Code in seinem „template“. Ihr „selector“ erscheint als Pseudo-Tag in umgebenden HTML-Code - hier wird jetzt endlich klar, wo das „app-root“ aus der „index.html“ zu finden ist! Der im „template“ festgelegte Code wird vom Browser nämlich anstelle dieses Pseudo-Tags eingesetzt. Wie das Beispiel zeigt, kann eine Komponente auch eigene „styles“ besitzen - also spezifischen CSS/SCSS - Code für diese Komponente. Template und Styles werden bei größeren Programmen auch meist in eigene Dateien ausgelagert, zumal manche Editoren mit solch „vermischten“ Komponentendateien auch nicht zurecht kommen. Das Beispiel zeigt auch ein erstes Beispiel von Variablen-Substitution: Die Variable (Property) „title“ im Code der Klasse wird im Template eingesetzt.

Interessant ist letztlich noch der <img>-Tag im Template, der ein Bitmap direkt als Base64-Code in die Komponente schreibt. So können auch kleine Grafiken/Logos direkt in „Chunks“ verpackt werden und müssen nicht explizit vom Browser abgefragt werden. Beim „favicon.ico“ geht das leider nicht, weil der Browser diese Datei stets explizit lädt.

Custom Styles mit Twitter Bootstrap

Wohl immer noch das populärste SCSS/CSS Framework (mit einem unglücklich gewählten Namen) ist Twitter's Bootstrap. Zwar bietet Google für Angular mit „Material“ ein überlegenes und auch für Mobilgeräte geeigneteres Stil-Framework, aber Bootstrap ist wohl immer noch stärker verbreitet - auch weil einfach verwendbar. Solch ein Framework ist ein Beispiel für eine Anwendung-übergreifende Konfiguration. Eine Firma / ein Kunde wird für all seine Apps immer das gleiche Stil-Framework mit angepasster „Corporate Identity“ (Schriften, Farben, Grundlayout) verwenden. So etwas gehört also nicht in jede einzelne Anwendung, sondern in die oben genannte „styles.scss“ Datei im „src“- Verzeichnis, die dann unverändert für alle erstellten Anwendungen gilt. In Teams werden „einfache Entwickler“ wohl kaum Schreibrechte außerhalb von „src/app“ bekommen, damit sie keine „unternehmenskritischen“ Einstellungen verändern können.

Auch mein Projekt SynthCommander baut so Twitter Bootstrap 4 in „styles.scss“ ein.

Zuerst kann man mit npm Bootstrap 4 in das Projekt installieren:

$ npm install bootstrap

Dabei mahnt npm noch „jquery“ und „popper“ als fehlende Abhängigkeiten an, die man als „peer dependencies“ selbst installieren muss. Ohne die populäre Javascript-Bibliothek „jquery“ geht bei Bootstrap tatsächlich vieles nicht, „popper“ kann man sich aber sparen, solange man keine aufpoppenden „globalen Dialoge“ benötigt. Also fehlt noch:

$ npm install jquery

Damit erscheint Bootstrap samt jquery zwar als installiert in der „package.json“, aber gehört nicht zum Projekt. Die Bootstrap- Quellcode-Dateien findet man unter „node-modules/bootstrap/scss“. In „src/styles.scss“ kann man also eintragen:

/* You can add global styles to this file, and also import other style files */
@import "~bootstrap/scss/bootstrap";

Damit wird die Datei „bootstrap.scss“ von „webpack“ gefunden, was zuerst eine Übersetzung aller darin referenzierten SCSS-Dateien erst zu CSS und dann zu Javascript-Code bewirkt. So eine Komplett-Übersetzung von Bootstrap 4 mag zeitaufwändig erscheinen, aber erlaubt Neudefinition grundlegender Gestaltungsmerkmale wie Schriften, Farben etc. - also die Einbringung einer individuellen, aber einheitlichen Corporate Identity für alle künftigen Kundenprojekte.

Sieht man sich die Seite nun an, hat sich bis auf die Schriftarten nicht viel geändert. Um Bootstrap 4 einzusetzen, muss man natürlich einige der CSS Klassen verwenden, die es definiert - wie im letzten Beispiel zu dieser kleinen Angular Einführung. Hier packe ich die Seite in einen Bootstrap-Container und lasse Linkliste durch Bootstrap verschönern. Das gesamte Template wird übrigens in der mit ECMAScript6 / Typescript „multiline string“ hingeschrieben. Stattdessen könnte ein „templateUrl“ statt dem „template“-Key das Template auch in eine externe HTML-Datei auslagern.

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <!--The content below is only a placeholder and can be replaced.-->
    <div class="container">
      <div style="text-align:center">
        <h1>Welcome to {{title}}!</h1>
        <img width="300" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
      </div>
      <h2>Here are some links to help you start: </h2>
      <ul class="list-group">
        <li class="list-group-item">
          <h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
        </li>
        <li class="list-group-item">
          <h2><a target="_blank" rel="noopener" href="https://angular.io/cli">CLI Documentation</a></h2>
        </li>
        <li  class="list-group-item">
          <h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
        </li>
      </ul>
    </div>
    
  `,
  styles: []
})
export class AppComponent {
  title = 'HelloAngular';
}

Die Seite sollte sich nun etwa so darstellen:

Weiter geht es mit der Implementierung von SynthCommander in Kapitel 2 des Tutorials …

start/themen/synthcommander.txt · Zuletzt geändert: 2020/06/08 18:55 von klaus