% Created 2017-10-13 Fri 22:28 \documentclass[presentation]{beamer} \usepackage{graphicx} \usepackage{longtable} \usepackage{float} \usepackage{wrapfig} \usepackage{rotating} \usepackage[normalem]{ulem} \usepackage{amsmath} \usepackage{textcomp} \usepackage{marvosym} \usepackage{wasysym} \usepackage{amssymb} \usepackage{hyperref} \usepackage[babelshorthands]{polyglossia} \tolerance=1000 \usepackage{tabu} \usepackage{minted} \setmainlanguage{english} \hypersetup{pdfauthor="Vasilij Schneidermann", pdftitle="Playing the piano with Kawa", colorlinks, linkcolor=black, urlcolor=blue} \usetheme{Rochester} \usecolortheme[RGB={87,83,170}]{structure} \author{Vasilij Schneidermann} \date{October 2017} \title{Playing the piano with Kawa} \hypersetup{ pdfkeywords={}, pdfsubject={}, pdfcreator={Emacs 25.3.1 (Org mode 8.2.10)}} \begin{document} \maketitle \begin{frame}{Outline} \tableofcontents \end{frame} \AtBeginSection{\frame{\sectionpage}} \section{Intro} \label{sec-1} \begin{frame}[fragile,label=sec-1-1]{About} \begin{itemize} \item Vasilij Schneidermann, 25 \item Software developer at bevuta IT, Cologne \item Still contracting at \texttt{\$BIGCORP} \item \texttt{v.schneidermann@gmail.com} \item \url{https://github.com/wasamasa} \item \url{http://emacshorrors.com/} \item \url{http://emacsninja.com/} \end{itemize} \end{frame} \begin{frame}[label=sec-1-2]{Why am I doing this?} \begin{itemize} \item I never received proper musical education \item I still want to cover songs and maybe compose tunes \item MIDI keyboards are bulky \item DAWs and GUI composition software is distracting \item Idea: Making my own thing with a text editor style workflow \item Reusing MIDI and OSC standards for control \end{itemize} \end{frame} \begin{frame}[label=sec-1-3]{Inspiration for this project} \begin{itemize} \item \url{https://blog.djy.io/alda-a-manifesto-and-gentle-introduction/} \item Pretty much what I looked for \item But: I'm not much of a fan of Clojure for this \item Overly complicated (networked, too much code, many dependencies) \item Lacks a feature I want (free play) \item Doesn't work properly for me (networking needs to be manually set up) \item How hard can it be to do this in a JVM Scheme? \end{itemize} \end{frame} \begin{frame}[fragile,label=sec-1-4]{Kawa} \begin{itemize} \item Been around since 1998, supporting R5RS, R6RS, R7RS with plenty SRFIs and own extensions \item Fast startup, compilation, decent speed \item Good interop syntax, emitting (anonymous) classes is simple \item High-quality, only found documentation bugs so far \item Biggest downside: Little own tooling (you're supposed to use \texttt{ant} for building JARs\ldots{}) \end{itemize} \end{frame} \begin{frame}[fragile,label=sec-1-5]{Meet waka!} \begin{itemize} \item Sorry about the name \item Requires JLine3 (terminal interaction) and \texttt{javax.sound.midi} (generate and play MIDI) \item Live demo time (let's hope this doesn't go wrong\ldots{}) \end{itemize} \end{frame} \section{Prerequisites} \label{sec-2} \begin{frame}[label=sec-2-1]{MIDI} \begin{itemize} \item The universal standard for transmitting music events \item MIDI controllers (keyboards) \item MIDI sequencers \item MIDI synthesizers \item Standard MIDI files \item MIDI cables \item Soundbanks, Soundfont format \end{itemize} \end{frame} \begin{frame}[label=sec-2-2]{MIDI limitations} \begin{itemize} \item 16 channels (each dedicated to a device/synth) \item Channel 9 is for percussion \item Tracks for logical grouping \item Files can have a single track, multiple tracks or multiple arrangements \item 128 instruments in 16 groups (GM standard) \item Special events for channel volume, pitch bending, tempo, \ldots{} \end{itemize} \end{frame} \begin{frame}[fragile,label=sec-2-3]{\texttt{javax.sound.midi}} \begin{itemize} \item Java SE has \texttt{javax.sound.sampled} (low-level audio playback) and \texttt{javax.sound.midi} (complete MIDI implementation) \item Supports: \begin{itemize} \item Parsing MIDI \item Loading soundbanks \item Generating sequences \item Playing them on a sequencer \item Writing them to disk \item Plenty of classes to represent many aspects of MIDI \end{itemize} \end{itemize} \end{frame} \begin{frame}[fragile,label=sec-2-4]{Bonus: Promises} \begin{itemize} \item Problem: Part of \texttt{javax.sound.midi} is async \item Interpreter quits after playing MIDI because it doesn't block until finish \item Solution: Make it block by forcing a promise and resolving it in the asynchronously called event handler when encountering MIDI end event \end{itemize} \end{frame} \begin{frame}[fragile,label=sec-2-5]{Bonus: Promises code} \begin{minted}[]{scheme} (let ((done (promise))) (sequence-thunk) (Sequencer:addMetaEventListener sequencer (lambda (message) (when (= (MetaMessage:getType message) END-OF-TRACK) (promise-set-value! done #t) (quit!)))) (Sequencer:start sequencer) (force done)) \end{minted} \end{frame} \begin{frame}[label=sec-2-6]{JLine3} \begin{itemize} \item Otherwise optional dependency for Kawa \item Free play mode requires reacting immediately to a pressed key \item Accomplished by enabling raw mode (and disabling it on quit) \item Catch exceptions to quit in a controlled manner \item Bonus features: Read line with line editing, persistent history \end{itemize} \end{frame} \section{Tour through waka} \label{sec-3} \begin{frame}[label=sec-3-1]{Features} \begin{itemize} \item Free play mode (type chars, hear notes) \item REPL mode (send a line, hear a line of notes) with history \item Parses a subset of Alda's syntax \item Basic error handling and messages \item Customizable defaults \item Batch playback of MIDI/waka files \item Conversion of waka files to MIDI files \item Implemented in < 1000 SLOC (Alda is almost 7000 SLOC) \end{itemize} \end{frame} \begin{frame}[fragile,label=sec-3-2]{Free play mode} \begin{itemize} \item Cheapo MIDI keyboard replacement \item Converts keyboard letter to MIDI note and creates a \texttt{NoteOn} event \item Prints the corresponding syntax for copying output into a waka file \item Lookup can be done in a custom map \item Octave switching with \texttt{<} and \texttt{>} \item Toggle to REPL mode with \texttt{C-SPC} \item Workflow: Try out suitable notes, switch to REPL mode after figuring out the right notes for a line \end{itemize} \end{frame} \begin{frame}[fragile,label=sec-3-3]{REPL mode} \begin{itemize} \item Parses a terse syntax adapted from Alda into AST for a sequence \item \texttt{RET} synthesizes MIDI sequence from AST and plays it back \item Fancy line editing provided by JLine3 \item Workflow: Edit current line and play it back with correct timing, copy the composed lines into a waka file \end{itemize} \end{frame} \begin{frame}[label=sec-3-4]{Batch mode} \begin{itemize} \item Parses a multi-track score into a list of sequences \item Converts those to a multi-track MIDI sequence \item Either plays it back or writes it to disk \item Future improvement: Dump AST for custom export (Lilypond?) \end{itemize} \end{frame} \begin{frame}[fragile,label=sec-3-5]{Syntax} \begin{itemize} \item Notes: \texttt{c d e f g a b} \item Setting a duration: \texttt{c1 c2 c4 c8 c16 c32} (last duration persists) \item Dotted notes (increase last duration by 1.5): \texttt{c d e.} \item Ties: \texttt{c1\textasciitilde{}1} \item Durations default to $1\over{4}$ and persist until next specified duration: \texttt{c4 d e f g2 g} \item Accidentals: \texttt{c c+ c- c\_} \end{itemize} \end{frame} \begin{frame}[fragile,label=sec-3-6]{Syntax} \begin{itemize} \item Chords: \texttt{c/e/g c/e-/g} \item Rests: \texttt{r4 r1\textasciitilde{}1 r} \item Bars (considered whitespace): \texttt{r1 r r r | r2 r | r4} \item Octave shift: \texttt{a > c e r2 e c < a} \item Octave change: \texttt{o0 c o2 c o4 c o6 c o8 c} \item Sexp: \texttt{(tempo 120) (tempo)} \item Comments: \texttt{\# you won't see me} \end{itemize} \end{frame} \begin{frame}[fragile,label=sec-3-7]{Sequences vs scores} \begin{itemize} \item Sequence consists of whitespace-separated items \item \texttt{c4 d e f | g2 g} \item Score consists of sequences, each preceded by a name \item \texttt{main: o4 c1 d e f g a b > c} \item \texttt{backing: o4 c1 < b a g f e d < c} \end{itemize} \end{frame} \begin{frame}[fragile,label=sec-3-8]{Lexing} \begin{itemize} \item Simple lexer pass to eliminate comments, split on whitespace, find tokens and read inline sexps \item State keeping with a string port \item Collect every token/sexp into a list and reverse it \item Create a token port with \texttt{peek-token} / \texttt{read-token} procedures \end{itemize} \end{frame} \begin{frame}[fragile,label=sec-3-9]{Lexing code} \begin{minted}[]{scheme} (let loop ((tokens '())) (let ((char (peek-char port))) (if (eof-object? char) (reverse tokens) (cond ((whitespace? char) (read-whitespace port) (loop tokens)) ((eqv? char #\#) (read-line port) (loop tokens)) ((eqv? char #\() (loop (cons (read port) tokens))) (else (loop (cons (read-token port) tokens))))))) \end{minted} \end{frame} \begin{frame}[fragile,label=sec-3-10]{Lexing code} \begin{minted}[]{scheme} (define (whitespace? char) (or (char-whitespace? char) (eqv? char #\|))) (define (read-whitespace port) (let loop () (when (whitespace? (peek-char port)) (read-char port)))) (define (read-token port) (let loop ((chars '())) (let ((char (peek-char port))) (if (and (not (eof-object? char)) (not (whitespace? char)) (not (memv char '(#\; #\()))) (loop (cons (read-char port) chars)) (list->string (reverse chars)))))) \end{minted} \end{frame} \begin{frame}[label=sec-3-11]{Parsing} \begin{itemize} \item Hand-written recursive descent parser \item Every grammar rule corresponds to a procedure receiving a token port or string port and returns part of the AST \item Makes up most of the code (> 200 SLOC) \item Errors halt parsing and bubble up to REPL / shell \end{itemize} \end{frame} \begin{frame}[fragile,label=sec-3-12]{Parsing code} \begin{minted}[]{scheme} (define (read-note port) (let ((key (read-key port))) (if key (let loop ((modifiers '())) (let ((modifier (read-modifier port))) (if modifier (loop (cons modifier modifiers)) `(note (key . ,key) ,@(reverse modifiers))))) #f))) (define (read-key port) (if (memv (peek-char port) '(#\a #\b #\c #\d #\e #\f #\g)) (read-char port) #f)) \end{minted} \end{frame} \begin{frame}[fragile,label=sec-3-13]{Error handling} \begin{itemize} \item Error handling code interwoven with parsing \item Extract current column from string port, point at erroneous char in token \item Last token held in a parameter \end{itemize} \begin{verbatim} midi> cxxx Error: Trailing garbage cxxx ^^^ \end{verbatim} \end{frame} \begin{frame}[fragile,label=sec-3-14]{Error handling code} \begin{minted}[]{scheme} (guard (ex ((parse-error-object? ex) (display "Error: ") (print (parse-error-message ex)) (let* ((token (parse-error-token ex)) (indent (port-column (parse-error-port ex))) (width (string-length token))) (print token) (display (make-string indent #\space)) (display (make-string (max (- width indent) 1) #\^)) (newline) (loop))) ...) ...) \end{minted} \end{frame} \section{Outro} \label{sec-4} \begin{frame}[label=sec-4-1]{Missing features} \begin{itemize} \item Auto-completion for sexps in REPL mode \item Channel and multiple instruments support, instrument aliases \item Percussion support (channel 9) \item Key signatures and naturals \item Legato / sustain (slurs) \item Repetition syntax for notes / subsequences \item Arbitrary durations, tuplets (CRAM) \item Arpeggiated chords, glissando/portamento, trills \end{itemize} \end{frame} \begin{frame}[label=sec-4-2]{Future Plans} \begin{itemize} \item Generate as good sound as Alda, steal other useful features \item Transcribe more sheet music \item Allow some way to import/export to other formats (MIDI import / Lilypond export) \item Debug sound issues (ideally by adding a debug mode and writing scripts that dissect generated MIDI) \item Better test suite \end{itemize} \end{frame} \begin{frame}[label=sec-4-3]{Singalong} \newfontfamily\fancy{Symbola} \begin{itemize} \item Let's play a classic! \item \fancy{🎜}\normalfont{} \emph{Fly Me To The Moon} \fancy{🎝}\normalfont \end{itemize} \end{frame} \begin{frame}[label=sec-4-4]{Questions?} \end{frame} % Emacs 25.3.1 (Org mode 8.2.10) \end{document}