Skip to content

Instantly share code, notes, and snippets.

@aavogt
Created July 19, 2025 20:54
Show Gist options
  • Select an option

  • Save aavogt/2f43ab311c566999fe08a4c6496ed5a6 to your computer and use it in GitHub Desktop.

Select an option

Save aavogt/2f43ab311c566999fe08a4c6496ed5a6 to your computer and use it in GitHub Desktop.
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Data.Binary
import Data.Binary.Get
import qualified Data.ByteString.Lazy as BL
import Data.Foldable
import Data.List
import Data.Maybe
import Data.Word (Word8)
import GHC.Generics
import System.Clock (Clock (Monotonic), getTime, toNanoSecs)
import System.Directory
-- MIDI Message data type
data MidiMessage = MidiMessage {status, data1, data2 :: !Word8}
deriving (Show, Generic, Binary)
streamMidi :: BL.ByteString -> [MidiMessage]
streamMidi = unfoldr f
where
f = either (const Nothing) (\(b, _, r) -> Just (r, b)) . runGetOrFail get
midiDevs :: IO [FilePath]
midiDevs = do
let fs = ["/dev/midi" ++ s | s <- "" : map show [0 .. 10]]
es <- mapM doesFileExist fs
return $ map snd $ filter fst $ zip es fs
timeStamp :: [MidiMessage] -> IO [(MidiMessage, Integer)]
timeStamp = mapM addTimestamp
where
addTimestamp msg = do
time <- getTime Monotonic
let timestamp = toNanoSecs time
return (msg, timestamp)
-- Main function
main :: IO ()
main = do
midiDevice <- fromMaybe "inpt" . listToMaybe <$> midiDevs
putStrLn midiDevice
input <- streamMidi <$> BL.readFile midiDevice
for_ input \p -> do
t <- getTime Monotonic
print (p, t)
(MidiMessage {status = 144, data1 = 64, data2 = 39},TimeSpec {sec = 44970, nsec = 664852329})
(MidiMessage {status = 128, data1 = 64, data2 = 99},TimeSpec {sec = 44970, nsec = 827328377})
(MidiMessage {status = 144, data1 = 62, data2 = 34},TimeSpec {sec = 44970, nsec = 840932261})
(MidiMessage {status = 128, data1 = 62, data2 = 80},TimeSpec {sec = 44971, nsec = 2938759})
(MidiMessage {status = 144, data1 = 64, data2 = 42},TimeSpec {sec = 44971, nsec = 23371563})
(MidiMessage {status = 128, data1 = 64, data2 = 94},TimeSpec {sec = 44971, nsec = 192795080})
(MidiMessage {status = 144, data1 = 67, data2 = 35},TimeSpec {sec = 44971, nsec = 254004591})
(MidiMessage {status = 128, data1 = 67, data2 = 97},TimeSpec {sec = 44971, nsec = 474090783})
(MidiMessage {status = 144, data1 = 65, data2 = 43},TimeSpec {sec = 44971, nsec = 495317590})
(MidiMessage {status = 128, data1 = 65, data2 = 105},TimeSpec {sec = 44971, nsec = 720551460})
(MidiMessage {status = 144, data1 = 62, data2 = 36},TimeSpec {sec = 44971, nsec = 741445738})
(MidiMessage {status = 128, data1 = 62, data2 = 93},TimeSpec {sec = 44971, nsec = 952744586})
(MidiMessage {status = 144, data1 = 65, data2 = 42},TimeSpec {sec = 44971, nsec = 982229875})
(MidiMessage {status = 128, data1 = 65, data2 = 104},TimeSpec {sec = 44972, nsec = 174855817})
(MidiMessage {status = 144, data1 = 64, data2 = 32},TimeSpec {sec = 44972, nsec = 195286771})
(MidiMessage {status = 128, data1 = 64, data2 = 89},TimeSpec {sec = 44972, nsec = 363475679})
(MidiMessage {status = 144, data1 = 65, data2 = 35},TimeSpec {sec = 44972, nsec = 390189291})
(MidiMessage {status = 128, data1 = 65, data2 = 106},TimeSpec {sec = 44972, nsec = 601167544})
(MidiMessage {status = 144, data1 = 60, data2 = 41},TimeSpec {sec = 44972, nsec = 607958437})
(MidiMessage {status = 128, data1 = 60, data2 = 91},TimeSpec {sec = 44972, nsec = 753628202})
(MidiMessage {status = 144, data1 = 64, data2 = 39},TimeSpec {sec = 44972, nsec = 786190195})
(MidiMessage {status = 128, data1 = 64, data2 = 95},TimeSpec {sec = 44972, nsec = 939147800})
(MidiMessage {status = 144, data1 = 60, data2 = 30},TimeSpec {sec = 44972, nsec = 967580622})
(MidiMessage {status = 128, data1 = 60, data2 = 45},TimeSpec {sec = 44973, nsec = 94612671})
(MidiMessage {status = 144, data1 = 67, data2 = 36},TimeSpec {sec = 44973, nsec = 128185503})
(MidiMessage {status = 128, data1 = 67, data2 = 99},TimeSpec {sec = 44973, nsec = 283770909})
(MidiMessage {status = 144, data1 = 64, data2 = 35},TimeSpec {sec = 44973, nsec = 307265803})
(MidiMessage {status = 128, data1 = 64, data2 = 80},TimeSpec {sec = 44973, nsec = 410990805})
(MidiMessage {status = 144, data1 = 67, data2 = 26},TimeSpec {sec = 44973, nsec = 463652838})
(MidiMessage {status = 128, data1 = 67, data2 = 88},TimeSpec {sec = 44973, nsec = 630358526})
(MidiMessage {status = 144, data1 = 62, data2 = 42},TimeSpec {sec = 44973, nsec = 689158148})
(MidiMessage {status = 128, data1 = 62, data2 = 56},TimeSpec {sec = 44973, nsec = 870544375})
(MidiMessage {status = 144, data1 = 65, data2 = 48},TimeSpec {sec = 44973, nsec = 950891275})
(MidiMessage {status = 128, data1 = 65, data2 = 105},TimeSpec {sec = 44974, nsec = 163107764})
(MidiMessage {status = 144, data1 = 62, data2 = 19},TimeSpec {sec = 44974, nsec = 207561607})
(MidiMessage {status = 128, data1 = 62, data2 = 67},TimeSpec {sec = 44974, nsec = 437011758})
(MidiMessage {status = 144, data1 = 67, data2 = 43},TimeSpec {sec = 44974, nsec = 469748576})
(MidiMessage {status = 128, data1 = 67, data2 = 106},TimeSpec {sec = 44974, nsec = 714929658})
(MidiMessage {status = 144, data1 = 65, data2 = 48},TimeSpec {sec = 44974, nsec = 718283393})
(MidiMessage {status = 144, data1 = 67, data2 = 55},TimeSpec {sec = 44974, nsec = 926657320})
(MidiMessage {status = 128, data1 = 65, data2 = 108},TimeSpec {sec = 44974, nsec = 933980919})
(MidiMessage {status = 128, data1 = 67, data2 = 105},TimeSpec {sec = 44975, nsec = 85117082})
name: midi-reader
version: 0.1.0.0
synopsis: Real-time MIDI reader for /dev/midi2
description: Reads and parses MIDI messages from /dev/midi2 device
license: BSD3
author: Your Name
maintainer: [email protected]
build-type: Simple
cabal-version: ">=1.10"
executables:
midi-reader:
main: main.hs
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- base >=4.7 && <5
- bytestring >=0.10
- transformers >=0.4
- binary
- directory
- clock
default-language: Haskell2010
% has a few typos
\version "2.18.2"
\paper {
page-breaking = #ly:page-turn-breaking
}
mbreak = { \break }
% missing numbers 46 through 54
ntwentyfour = { g16 a g a b c b c d c d c b a b a |
g a b a b c d c d c b c b a g c |
g a b c a b c d b c d c d c b a |
b a b c d c b c b c b a g a b a |
g a b g b a g b a b c a c b a c | b c d b d c b d a b c a c b a c | % end 28
g b a g b g a b a c b a c a b c | b d c b d b c d a c b a c a b c |
g a g b a g b g a b a c b a c a | b c b d c b d b a b a c b a c a | % end 30
g b a b a c b c b d c d a c b c | b g b a c a c b d b c b c a b a | % end 31
g a b a g b a g a b c b a c b a | b c d c b d c b a b c b a c b a |
b a g a b g a b c b a b c a b c | d c b c d b c d c b a b c a b c |
}
ntwentyfourl = \relative c' \ntwentyfour
\score {
\new PianoStaff <<
\new Staff \relative c'' \ntwentyfour
\new Staff \ntwentyfourl
>> \header{ opus = "Nr24" }
}
nthirtyfive = { g8 b a g a b a c b a b c |
b d c b c d a c b a b c |
b g a b a g c a b c b a | d b c d c b c a b c b a | g1. | % end 35
g8 b a b a g a c b c b a | b d c d c b a c b c b a |
b g a d a b c a b a b c | d b c b c d c a b c b a | b1. | % end
g8 a g b a g a b a c b a | b c b d c b a b a c b a |
b a b g a b c b c a b c |
d c d b c d c a c b d c | b1.
}
\score {
\new PianoStaff <<
\time 12/8
\new Staff \relative c'' \nthirtyfive
\new Staff \relative c' \nthirtyfive
>> \header{ opus = "Nr35" }
}
nfiftysevenr = {
% start 57
c16 e g e c e g e d f g f d f g f |
g e c e g e c e g f d f g f d f |
c g' e g c, g' e g d g d f d g d f |
g c, e c g' c, e c g' d f d g d f d | % end 57
c g' e c g' e c g' d g f d g f d g c, e g c, e g c, e d f g f d f g f |
e g c, g' e g c, g' f g d g f g d f g e g c, g' e g c, g' f g d g f g d | %end 58
c g' e c g' d g c, d f g f g f g d |
c e g e g d g c, d f g f g e g d |
c g' d g e g c, g' d g e g f g d f |
g c, g' d g e g c, g' d g e g f g d | % end 59
}
% almost the same, except c g picks the right
% octave in the bass clef
nfiftysevenl = {
c16 e g e c e g e d f g f d f g f |
g e c e g e c e g f d f g f d f |
c g e g c g e g d g d f d g d f |
g c e c g c e c g d f d g d f d | % end 57
c g e c g e c g d g f d g f d g c e g c e g c e d f g f d f g f |
e g c g e g c g f g d g f g d f g e g c g e g c g f g d g f g d | %end 58
c g e c g d g c d f g f g f g d |
c e g e g d g c d f g f g e g d |
c g d g e g c g d g e g f g d f |
g c g d g e g c g d g e g f g d | % end 59
}
\score {
\new PianoStaff <<
\new Staff \relative c' \nfiftysevenr
\new Staff { \relative c' \clef bass \nfiftysevenl }
>> \header{ opus = "Nr57" }
}
nsixtyl = {
c8 e g e d e d f g f e f g e c e d e g f d f e f c e c g e g d f d g f g e c e g e g f d f g f g | % end 60
c e g e f g d f g f e d c e g e c g c f g f g c c g e c d f d g f c f g c g e c g e d g f d g f | % end 61 needs c g' on right
c g c d g d e g e f g f e g e d g d f g f d g d e g e d g c f g f e g d c g e d g c d g f e g d | <c e>1.
}
nsixtyr = {
c8 e g e d e d f g f e f g e c e d e g f d f e f c e c g' e g d f d g f g e c e g e g f d f g f g | % end 60
c, e g e f g d f g f e d c e g e c g' c, f g f g c, c g' e c d f d g f c f g c, g' e c g' e d g f d g f | % end 61 needs c g' on right
c g' c, d g d e g e f g f e g e d g d f g f d g d e g e d g c, f g f e g d c g' e d g c, d g f e g d | c1.
}
\score {
\new PianoStaff <<
\time 12/8
\new Staff \relative c' \nsixtyr
\new Staff { \clef bass \nsixtyl }
>> \header{ opus = "Nr60" }
}
nsixtythrer = {
c4 d e c d e f d e f g e f d c2 |
c1 b c b2 c |
c4 c8 d e4 c | d d8 e f4 d | e e8 f g4 e | f d8 e c2 |
c1 b c b2 c | % end 66
c8 d c d e4 c | d8 e d e f4 d | e8 f e f g4 e | f8 e d e c2 |
c1 b c b2 c |
c8 d c d e d e c | d e d e f e f d | e f e f g f g e | f e f d c2 | % end 69
c1 b c b2 c |
c8 d e d c d e c | d e f e d e f d | e f g f e f g f d e f d c2 |
}
nsixtythrel = \relative {
c'1 b c g2 c |
c4 d e c d e f d e f g e f d c2 |
c1 b c g2 c |
c4 c8 d e4 c | d d8 e f4 d | e e8 f g4 e | f d8 e c2 |
c1 b c g2 c |
c8 d c d e4 c | d8 e d e f4 d | e8 f e f g4 e | f8 e d e c2 |
c1 b c g2 c |
c8 d c d e d e c | d e d e f e f d | e f e f g f g e f e f d c2 |
c1 b c g2 c
}
\score {
\new PianoStaff <<
\new Staff \relative c'' \nsixtythrer
\new Staff { \relative c'' \clef bass \nsixtythrel }
>> \header{ opus = "Nr63" }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment