! Copyright (C) 2026 Serre
! See https://factorcode.org/license.txt for BSD license.
USING: accessors alien.c-types alien.data alien.syntax arrays
assocs byte-arrays calendar classes.struct hex-strings kernel
math math.order math.parser memory namespaces ranges sequences
shuffle strings threads windows.types ;
IN: 5line.midi
! === Streamed MIDI output via Windows winmm.lib MIDI API
! == C Bindings
LIBRARY: winmm
TYPEDEF: uint MMRESULT
FUNCTION: UINT midiOutGetNumDevs ( )
STRUCT: MIDIOUTCAPSA
{ wMid WORD }
{ wPid WORD }
{ vDriverVersion UINT }
{ szPname CHAR[32] }
{ wTechnology WORD }
{ wVoices WORD }
{ wNotes WORD }
{ wChannelMask WORD }
{ dwSupport DWORD } ;
FUNCTION: MMRESULT midiOutGetDevCapsA (
UINT uDeviceID,
PVOID pmoc,
UINT cbmoc
)
FUNCTION: MMRESULT midiOutOpen (
PVOID* phmo,
UINT uDeviceID,
DWORD_PTR dwCallback,
DWORD_PTR dwInstance,
DWORD fdwOpen
)
FUNCTION: MMRESULT midiOutClose (
HANDLE hmo
)
FUNCTION: MMRESULT midiOutShortMsg (
HANDLE hmo,
DWORD dwMsg
)
! == Midi Device Management
: midi-out-caps ( int -- struct )
MIDIOUTCAPSA <struct> dup size
over [ midiOutGetDevCapsA drop ] dip ;
: midi-out-name ( int -- string )
midi-out-caps szPname>> [ 0 = ] reject >string ;
: all-midi-outs ( -- alist )
midiOutGetNumDevs [0..b)
dup [ midi-out-name ] map zip ;
INITIALIZED-SYMBOL: active-midi-out [ f ]
: (open-midi-out) ( device-id -- )
[ f void* <ref> dup ] dip 0 0 0 midiOutOpen
drop void* deref active-midi-out set-global ;
! (todo: Error Handling)
: close-midi-out ( -- )
active-midi-out get-global midiOutClose drop
f active-midi-out set-global ;
! (todo: Error Handling, Destructors?)
: open-midi-out ( device-id -- )
active-midi-out get-global [ close-midi-out ] when
(open-midi-out) ;
! == MIDI Messages (redundant with base midi vocab?)
CONSTANT: note-on 0b1001
CONSTANT: note-off 0b1000
CONSTANT: prog-chg 0b1100
: on-channel ( on? ch. -- bytes )
[ note-on note-off ? 4 shift ] [ 1 max 16 min 1 - ] bi* + ;
: (chg-prog) ( prog ch. -- bytes )
[ 1 - ] bi@ prog-chg 4 shift + 2array >byte-array ;
: chg-prog ( prog ch. -- )
(chg-prog) bytes>hex-string hex>
[ active-midi-out get-global ] dip
midiOutShortMsg drop ;
CONSTANT: default-vel 127
: dial-note-bytes ( note on? ch. -- byte-array )
on-channel swap 127 min default-vel
spin 3array >byte-array ;
: dial-note ( note on? ch. -- )
dial-note-bytes bytes>hex-string hex>
[ active-midi-out get-global ] dip
midiOutShortMsg drop ;
: midi-panic* ( ch. -- )
[ 0 ..= 127 ] dip '[ f _ dial-note ] each ;
: midi-panic ( -- )
0 ..= 16 [ midi-panic* ] each ;
: with-midi-out ( quot device-id -- )
! Totally broken, either exits early or hangs forever.
! There's some lag inherent(?) to MIDI we need to compensate for
open-midi-out
self [ resume stop ] curry compose "with-midi-out"
spawn suspend drop 1 seconds sleep
close-midi-out ;
: dial-on ( note ch. -- ) [ t ] dip dial-note ;
: dial-off-in ( note dt ch. -- )
swap [ sleep [ f ] dip dial-note stop ]
3curry "Note Off" spawn drop ;
: dial-for ( note dt ch. -- )
[ nip dial-on ] [ dial-off-in ] 3bi ;
: dial-notes-for ( note/dt-pairs ch. -- )
'[ first2 _ dial-for ] each ;