packages feed

libremidi (empty) → 0.1.0

raw patch · 15 files changed

+3896/−0 lines, 15 filesdep +basedep +containersdep +data-default

Dependencies added: base, containers, data-default, libremidi, mtl, stm, tasty, tasty-hunit, text, transformers

Files

+ exe/Main.hs view
@@ -0,0 +1,26 @@+module Main+  ( main+  )+where++import Control.Monad.IO.Class (liftIO)+import Data.Foldable (traverse_)+import Data.Sequence qualified as Seq+import Data.Text.IO qualified as TIO+import Libremidi.Api qualified as LMA+import Libremidi.Simple qualified as LMS++main :: IO ()+main = flip LMS.runMidiM LMS.stderrLogFun $ do+  liftIO (putStrLn "* In ports:")+  inPorts <- LMS.listInPorts+  traverse_ (liftIO . TIO.putStrLn . fst) inPorts+  liftIO (putStrLn "* Out ports:")+  outPorts <- LMS.listOutPorts+  traverse_ (liftIO . TIO.putStrLn . fst) outPorts+  liftIO (putStrLn "* Opening first out port")+  let op = snd (Seq.index outPorts 0)+  oh <- LMS.openOutPort op+  liftIO (putStrLn "* Connected")+  liftIO (LMA.freeOutHandle oh)+  liftIO (putStrLn "* Freed")
+ libremidi.cabal view
@@ -0,0 +1,201 @@+cabal-version: 2.2++-- This file has been generated from package.yaml by hpack version 0.37.0.+--+-- see: https://github.com/sol/hpack++name:           libremidi+version:        0.1.0+synopsis:       libremidi bindings for haskell+description:    Please see the README on GitHub at <https://github.com/ejconlon/libremidi-haskell#readme>+homepage:       https://github.com/ejconlon/libremidi-haskell#readme+bug-reports:    https://github.com/ejconlon/libremidi-haskell/issues+author:         Eric Conlon+maintainer:     ejconlon@gmail.com+copyright:      (c) 2024 Eric Conlon+license:        BSD-3-Clause+build-type:     Simple+tested-with:+    GHC == 9.8.4++source-repository head+  type: git+  location: https://github.com/ejconlon/libremidi-haskell++library+  exposed-modules:+      Libremidi.Api+      Libremidi.Common+      Libremidi.Foreign+      Libremidi.Simple+  other-modules:+      Paths_libremidi+  autogen-modules:+      Paths_libremidi+  hs-source-dirs:+      src+  default-extensions:+      BangPatterns+      ConstraintKinds+      DataKinds+      DeriveFunctor+      DeriveFoldable+      DeriveGeneric+      DeriveTraversable+      DerivingStrategies+      DerivingVia+      FlexibleContexts+      FlexibleInstances+      FunctionalDependencies+      GADTs+      GeneralizedNewtypeDeriving+      ImportQualifiedPost+      LambdaCase+      KindSignatures+      MultiParamTypeClasses+      MultiWayIf+      PatternSynonyms+      Rank2Types+      ScopedTypeVariables+      StandaloneDeriving+      StandaloneKindSignatures+      TupleSections+      TypeApplications+      TypeOperators+      TypeFamilies+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -fno-warn-unused-top-binds+  include-dirs:+      vendor+  cxx-sources:+      vendor/libremidi/client.cpp+      vendor/libremidi/libremidi-c.cpp+      vendor/libremidi/libremidi.cpp+      vendor/libremidi/midi_in.cpp+      vendor/libremidi/midi_out.cpp+      vendor/libremidi/observer.cpp+      vendor/libremidi/reader.cpp+      vendor/libremidi/writer.cpp+  extra-libraries:+      stdc+++  build-depends:+      base >=4.12 && <5+    , containers >=0.6.7 && <0.7+    , data-default >=0.7.1.1 && <0.8+    , mtl >=2.3.1 && <2.4+    , stm >=2.5.1.0 && <2.6+    , text >=2.0.2 && <2.2+    , transformers >=0.6.1 && <0.7+  default-language: GHC2021+  if os(darwin)+    ghc-options: -pgmcxx=clang+++    cxx-options: -std=c++20 -DLIBREMIDI_COREMIDI=1+    frameworks:+        CoreMIDI CoreAudio CoreFoundation+    ld-options: -Wl,-keep_dwarf_unwind+  if os(linux)+    ghc-options: -pgmcxx=clang+++    cxx-options: -std=c++20 -DLIBREMIDI_PIPEWIRE=1 -DLIBREMIDI_HAS_UDEV=1+    pkgconfig-depends:+        libpipewire-0.3+      , udev++executable libremidi-exe+  main-is: Main.hs+  other-modules:+      Paths_libremidi+  autogen-modules:+      Paths_libremidi+  hs-source-dirs:+      exe+  default-extensions:+      BangPatterns+      ConstraintKinds+      DataKinds+      DeriveFunctor+      DeriveFoldable+      DeriveGeneric+      DeriveTraversable+      DerivingStrategies+      DerivingVia+      FlexibleContexts+      FlexibleInstances+      FunctionalDependencies+      GADTs+      GeneralizedNewtypeDeriving+      ImportQualifiedPost+      LambdaCase+      KindSignatures+      MultiParamTypeClasses+      MultiWayIf+      PatternSynonyms+      Rank2Types+      ScopedTypeVariables+      StandaloneDeriving+      StandaloneKindSignatures+      TupleSections+      TypeApplications+      TypeOperators+      TypeFamilies+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -fno-warn-unused-top-binds -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.12 && <5+    , containers >=0.6.7 && <0.7+    , data-default >=0.7.1.1 && <0.8+    , libremidi+    , mtl >=2.3.1 && <2.4+    , stm >=2.5.1.0 && <2.6+    , text >=2.0.2 && <2.2+    , transformers >=0.6.1 && <0.7+  default-language: GHC2021++test-suite libremidi-test+  type: exitcode-stdio-1.0+  main-is: Main.hs+  other-modules:+      Paths_libremidi+  autogen-modules:+      Paths_libremidi+  hs-source-dirs:+      test+  default-extensions:+      BangPatterns+      ConstraintKinds+      DataKinds+      DeriveFunctor+      DeriveFoldable+      DeriveGeneric+      DeriveTraversable+      DerivingStrategies+      DerivingVia+      FlexibleContexts+      FlexibleInstances+      FunctionalDependencies+      GADTs+      GeneralizedNewtypeDeriving+      ImportQualifiedPost+      LambdaCase+      KindSignatures+      MultiParamTypeClasses+      MultiWayIf+      PatternSynonyms+      Rank2Types+      ScopedTypeVariables+      StandaloneDeriving+      StandaloneKindSignatures+      TupleSections+      TypeApplications+      TypeOperators+      TypeFamilies+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -fno-warn-unused-top-binds -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.12 && <5+    , containers >=0.6.7 && <0.7+    , data-default >=0.7.1.1 && <0.8+    , libremidi+    , mtl >=2.3.1 && <2.4+    , stm >=2.5.1.0 && <2.6+    , tasty+    , tasty-hunit+    , text >=2.0.2 && <2.2+    , transformers >=0.6.1 && <0.7+  default-language: GHC2021
+ src/Libremidi/Api.hs view
@@ -0,0 +1,509 @@+-- | Mid-level interface to Libremidi.+-- Operations in IO do not fail.+-- Operations in ErrM can possibly fail.+module Libremidi.Api where++import Control.Monad.IO.Class (liftIO)+import Data.Coerce (coerce)+import Data.Default (Default (..))+import Data.Foldable (traverse_)+import Data.Int (Int64)+import Data.Maybe (fromMaybe)+import Data.Proxy (Proxy (..))+import Data.Text (Text)+import Data.Text.Foreign qualified as TF+import Foreign.Concurrent qualified as FC+import Foreign.ForeignPtr (ForeignPtr, finalizeForeignPtr, withForeignPtr)+import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)+import Foreign.Ptr (FunPtr, Ptr, castPtrToFunPtr, nullPtr)+import Libremidi.Common+  ( BitEnum (..)+  , Cb+  , ErrM+  , Field+  , MallocPtr (..)+  , UniquePtr+  , assocM+  , checkM+  , consumeCb+  , consumeUniquePtr+  , fromCBool+  , fromCLong+  , pokeField+  , takeM+  , textM+  , toCBool+  , toCLong+  , toCSize+  , unRunErrM+  , withCb+  )+import Libremidi.Foreign qualified as LMF++data TimestampMode+  = TimestampModeNone+  | TimestampModeRelative+  | TimestampModeAbsolute+  | TimestampModeSystemMono+  | TimestampModeAudioFrame+  | TimestampModeCustom+  deriving stock (Eq, Ord, Show, Enum, Bounded)++instance BitEnum LMF.TimestampMode TimestampMode where+  fromBitEnum = \case+    a | a == LMF.tmNoTimestamp -> Just TimestampModeNone+    a | a == LMF.tmRelative -> Just TimestampModeRelative+    a | a == LMF.tmAbsolute -> Just TimestampModeAbsolute+    a | a == LMF.tmSystemMonotonic -> Just TimestampModeSystemMono+    a | a == LMF.tmAudioFrame -> Just TimestampModeAudioFrame+    a | a == LMF.tmCustom -> Just TimestampModeCustom+    _ -> Nothing+  toBitEnum = \case+    TimestampModeNone -> LMF.tmNoTimestamp+    TimestampModeRelative -> LMF.tmRelative+    TimestampModeAbsolute -> LMF.tmAbsolute+    TimestampModeSystemMono -> LMF.tmSystemMonotonic+    TimestampModeAudioFrame -> LMF.tmAudioFrame+    TimestampModeCustom -> LMF.tmCustom++data Api+  = ApiUnspecified+  | ApiCoremidi+  | ApiAlsaSeq+  | ApiAlsaRaw+  | ApiJackMidi+  | ApiWindowsMm+  | ApiWindowsUwp+  | ApiWebmidi+  | ApiPipewire+  | ApiAlsaRawUmp+  | ApiAlsaSeqUmp+  | ApiCoremidiUmp+  | ApiWindowsMidiServices+  | ApiDummy+  deriving stock (Eq, Ord, Show, Enum, Bounded)++instance BitEnum LMF.Api Api where+  fromBitEnum = \case+    a | a == LMF.apiUnspecified -> Just ApiUnspecified+    a | a == LMF.apiCoremidi -> Just ApiCoremidi+    a | a == LMF.apiAlsaSeq -> Just ApiAlsaSeq+    a | a == LMF.apiAlsaRaw -> Just ApiAlsaRaw+    a | a == LMF.apiJackMidi -> Just ApiJackMidi+    a | a == LMF.apiWindowsMm -> Just ApiWindowsMm+    a | a == LMF.apiWindowsUwp -> Just ApiWindowsUwp+    a | a == LMF.apiWebmidi -> Just ApiWebmidi+    a | a == LMF.apiPipewire -> Just ApiPipewire+    a | a == LMF.apiAlsaRawUmp -> Just ApiAlsaRawUmp+    a | a == LMF.apiAlsaSeqUmp -> Just ApiAlsaSeqUmp+    a | a == LMF.apiCoremidiUmp -> Just ApiCoremidiUmp+    a | a == LMF.apiWindowsMidiServices -> Just ApiWindowsMidiServices+    a | a == LMF.apiDummy -> Just ApiDummy+    _ -> Nothing+  toBitEnum = \case+    ApiUnspecified -> LMF.apiUnspecified+    ApiCoremidi -> LMF.apiCoremidi+    ApiAlsaSeq -> LMF.apiAlsaSeq+    ApiAlsaRaw -> LMF.apiAlsaRaw+    ApiJackMidi -> LMF.apiJackMidi+    ApiWindowsMm -> LMF.apiWindowsMm+    ApiWindowsUwp -> LMF.apiWindowsUwp+    ApiWebmidi -> LMF.apiWebmidi+    ApiPipewire -> LMF.apiPipewire+    ApiAlsaRawUmp -> LMF.apiAlsaRawUmp+    ApiAlsaSeqUmp -> LMF.apiAlsaSeqUmp+    ApiCoremidiUmp -> LMF.apiCoremidiUmp+    ApiWindowsMidiServices -> LMF.apiWindowsMidiServices+    ApiDummy -> LMF.apiDummy++data ConfigType+  = ConfigTypeObserver+  | ConfigTypeInput+  | ConfigTypeOutput+  deriving stock (Eq, Ord, Show, Enum, Bounded)++instance BitEnum LMF.ConfigType ConfigType where+  fromBitEnum = \case+    a | a == LMF.ctObserver -> Just ConfigTypeObserver+    a | a == LMF.ctInput -> Just ConfigTypeInput+    a | a == LMF.ctOutput -> Just ConfigTypeOutput+    _ -> Nothing+  toBitEnum = \case+    ConfigTypeObserver -> LMF.ctObserver+    ConfigTypeInput -> LMF.ctInput+    ConfigTypeOutput -> LMF.ctOutput++data Version+  = VersionMidi1+  | VersionMidi2+  deriving stock (Eq, Ord, Show, Enum, Bounded)++instance BitEnum LMF.Version Version where+  fromBitEnum = \case+    a | a == LMF.verMidi1 -> Just VersionMidi1+    a | a == LMF.verMidi2 -> Just VersionMidi2+    _ -> Nothing+  toBitEnum = \case+    VersionMidi1 -> LMF.verMidi1+    VersionMidi2 -> LMF.verMidi2++type Timestamp = Int64++type InPort = ForeignPtr LMF.InPort++freeInPort :: InPort -> IO ()+freeInPort = finalizeForeignPtr++cloneInPort' :: Ptr LMF.InPort -> ErrM InPort+cloneInPort' = takeM . LMF.libremidi_midi_in_port_clone++cloneInPort :: InPort -> ErrM InPort+cloneInPort = assocM cloneInPort'++inPortName' :: Ptr LMF.InPort -> ErrM Text+inPortName' = textM . LMF.libremidi_midi_in_port_name++inPortName :: InPort -> ErrM Text+inPortName = assocM inPortName'++type OutPort = ForeignPtr LMF.OutPort++freeOutPort :: OutPort -> IO ()+freeOutPort = finalizeForeignPtr++cloneOutPort' :: Ptr LMF.OutPort -> ErrM OutPort+cloneOutPort' = takeM . LMF.libremidi_midi_out_port_clone++cloneOutPort :: OutPort -> ErrM OutPort+cloneOutPort = assocM cloneOutPort'++outPortName' :: Ptr LMF.OutPort -> ErrM Text+outPortName' = textM . LMF.libremidi_midi_out_port_name++outPortName :: OutPort -> ErrM Text+outPortName = assocM outPortName'++data LogLvl = LogLvlWarn | LogLvlErr+  deriving stock (Eq, Ord, Show, Enum, Bounded)++type LogFun = LogLvl -> Text -> IO ()++newLogCb :: LogFun -> LogLvl -> IO (Cb LMF.LogFun)+newLogCb f lvl = LMF.newLogCb $ \_ s l _ -> do+  msg <- liftIO (TF.fromPtr (coerce s) (fromIntegral l))+  f lvl msg++data ObsAct = ObsActAdd | ObsActRem+  deriving stock (Eq, Ord, Show, Enum, Bounded)++type ObsFun p = ObsAct -> Ptr p -> IO ()++type ObsInFun = ObsFun LMF.InPort++type ObsOutFun = ObsFun LMF.OutPort++newObsCb :: ObsFun p -> ObsAct -> IO (Cb (LMF.ObsFun p))+newObsCb f = LMF.newObsCb . const . f++type EnumFun p = Ptr p -> IO ()++type EnumInFun = EnumFun LMF.InPort++type EnumOutFun = EnumFun LMF.OutPort++newEnumCb :: EnumFun p -> IO (Cb (LMF.ObsFun p))+newEnumCb = LMF.newObsCb . const++data ObsConfig = ObsConfig+  { ocOnErr :: !(Maybe (Cb LMF.LogFun))+  , ocOnWarn :: !(Maybe (Cb LMF.LogFun))+  , ocInAdd :: !(Maybe (Cb (LMF.ObsFun LMF.InPort)))+  , ocInRem :: !(Maybe (Cb (LMF.ObsFun LMF.InPort)))+  , ocOutAdd :: !(Maybe (Cb (LMF.ObsFun LMF.OutPort)))+  , ocOutRem :: !(Maybe (Cb (LMF.ObsFun LMF.OutPort)))+  , ocTrackHardware :: !Bool+  , ocTrackVirtual :: !Bool+  , ocTrackAny :: !Bool+  , ocNotifInCon :: !Bool+  }+  deriving stock (Eq)++instance Default ObsConfig where+  def =+    ObsConfig+      { ocOnErr = Nothing+      , ocOnWarn = Nothing+      , ocInAdd = Nothing+      , ocInRem = Nothing+      , ocOutAdd = Nothing+      , ocOutRem = Nothing+      , ocTrackHardware = True+      , ocTrackVirtual = True+      , ocTrackAny = True+      , ocNotifInCon = True+      }++setObsLogCb :: LogFun -> ObsConfig -> IO ObsConfig+setObsLogCb f oc = do+  onErr <- newLogCb f LogLvlErr+  onWarn <- newLogCb f LogLvlWarn+  pure $+    oc+      { ocOnErr = Just onErr+      , ocOnWarn = Just onWarn+      }++setObsInCb :: ObsInFun -> ObsConfig -> IO ObsConfig+setObsInCb f oc = do+  onAdd <- newObsCb f ObsActAdd+  onRem <- newObsCb f ObsActRem+  pure $+    oc+      { ocInAdd = Just onAdd+      , ocInRem = Just onRem+      }++setObsOutCb :: ObsOutFun -> ObsConfig -> IO ObsConfig+setObsOutCb f oc = do+  onAdd <- newObsCb f ObsActAdd+  onRem <- newObsCb f ObsActRem+  pure $+    oc+      { ocOutAdd = Just onAdd+      , ocOutRem = Just onRem+      }++mkObsConfig :: ObsConfig -> IO (ForeignPtr LMF.ObsConfig)+mkObsConfig oc = do+  f <- mallocPtr (Proxy @LMF.ObsConfig)+  withForeignPtr f $ \p -> do+    let+      setFun :: forall x. Field LMF.ObsConfig (FunPtr x) -> Cb x -> IO ()+      setFun field cb = do+        q <- consumeCb cb+        let w = castPtrToFunPtr (unsafeForeignPtrToPtr q)+        pokeField field p w+        FC.addForeignPtrFinalizer f (finalizeForeignPtr q)+    traverse_ (setFun LMF.ocOnErr) (ocOnErr oc)+    traverse_ (setFun LMF.ocOnWarn) (ocOnWarn oc)+    traverse_ (setFun LMF.ocInAdd) (ocInAdd oc)+    traverse_ (setFun LMF.ocInRem) (ocInRem oc)+    traverse_ (setFun LMF.ocOutAdd) (ocOutAdd oc)+    traverse_ (setFun LMF.ocOutRem) (ocOutRem oc)+    pokeField LMF.ocTrackHardware p (toCBool (ocTrackHardware oc))+    pokeField LMF.ocTrackVirtual p (toCBool (ocTrackVirtual oc))+    pokeField LMF.ocTrackAny p (toCBool (ocTrackAny oc))+    pokeField LMF.ocNotifInCon p (toCBool (ocNotifInCon oc))+  pure f++data MidiPort+  = MidiPortIn !(UniquePtr LMF.InPort)+  | MidiPortOut !(UniquePtr LMF.OutPort)+  deriving stock (Eq)++data MsgFun+  = MsgFun1 (Cb (LMF.MsgFun LMF.Sym1))+  | MsgFun2 (Cb (LMF.MsgFun LMF.Sym2))+  deriving stock (Eq)++msgFunVersion :: MsgFun -> Version+msgFunVersion = \case+  MsgFun1 _ -> VersionMidi1+  MsgFun2 _ -> VersionMidi2++data MidiConfig = MidiConfig+  { mcVersion :: !(Maybe Version)+  , mcPort :: !(Maybe MidiPort)+  , mcOnMsg :: !(Maybe MsgFun)+  , mcGetTime :: !(Maybe (Cb LMF.TimeFun))+  , mcOnErr :: !(Maybe (Cb LMF.LogFun))+  , mcOnWarn :: !(Maybe (Cb LMF.LogFun))+  , mcVirtualPort :: !Bool+  , mcIgnoreSysex :: !Bool+  , mcIgnoreTiming :: !Bool+  , mcIgnoreSensing :: !Bool+  , mcTimestamps :: !TimestampMode+  }+  deriving stock (Eq)++instance Default MidiConfig where+  def =+    MidiConfig+      { mcVersion = Nothing+      , mcPort = Nothing+      , mcOnMsg = Nothing+      , mcGetTime = Nothing+      , mcOnErr = Nothing+      , mcOnWarn = Nothing+      , mcVirtualPort = False+      , mcIgnoreSysex = False+      , mcIgnoreTiming = False+      , mcIgnoreSensing = True+      , mcTimestamps = TimestampModeAbsolute+      }++setMidiLogCb :: LogFun -> MidiConfig -> IO MidiConfig+setMidiLogCb f mc = do+  onErr <- newLogCb f LogLvlErr+  onWarn <- newLogCb f LogLvlWarn+  pure $+    mc+      { mcOnErr = Just onErr+      , mcOnWarn = Just onWarn+      }++mkMidiConfig :: MidiConfig -> IO (ForeignPtr LMF.MidiConfig)+mkMidiConfig mc = do+  let ver = fromMaybe (maybe VersionMidi1 msgFunVersion (mcOnMsg mc)) (mcVersion mc)+  f <- mallocPtr (Proxy @LMF.MidiConfig)+  withForeignPtr f $ \p -> do+    let+      setFun :: forall x. Field LMF.MidiConfig (FunPtr x) -> Cb x -> IO ()+      setFun field cb = do+        q <- consumeCb cb+        let w = castPtrToFunPtr (unsafeForeignPtrToPtr q)+        pokeField field p w+        FC.addForeignPtrFinalizer f (finalizeForeignPtr q)+      setPtr :: forall x. Field LMF.MidiConfig (Ptr x) -> UniquePtr x -> IO ()+      setPtr field u = do+        q <- consumeUniquePtr u+        let w = unsafeForeignPtrToPtr q+        pokeField field p w+        FC.addForeignPtrFinalizer f (finalizeForeignPtr q)+    case mcPort mc of+      Just (MidiPortIn ip) -> setPtr LMF.mcInPort ip+      Just (MidiPortOut op) -> setPtr LMF.mcOutPort op+      Nothing -> pure ()+    case mcOnMsg mc of+      Just (MsgFun1 cb1) -> setFun LMF.mcOnMsg1 cb1+      Just (MsgFun2 cb2) -> setFun LMF.mcOnMsg2 cb2+      Nothing -> pure ()+    traverse_ (setFun LMF.mcGetTime) (mcGetTime mc)+    traverse_ (setFun LMF.mcOnErr) (mcOnErr mc)+    traverse_ (setFun LMF.mcOnWarn) (mcOnWarn mc)+    pokeField LMF.mcVersion p (toBitEnum ver)+    pokeField LMF.mcVirtualPort p (toCBool (mcVirtualPort mc))+    pokeField LMF.mcIgnoreSysex p (toCBool (mcIgnoreSysex mc))+    pokeField LMF.mcIgnoreTiming p (toCBool (mcIgnoreTiming mc))+    pokeField LMF.mcIgnoreSensing p (toCBool (mcIgnoreSensing mc))+    pokeField LMF.mcTimestamps p (toBitEnum (mcTimestamps mc))+  pure f++data ApiConfig = ApiConfig+  { acApi :: !Api+  , acConfigType :: !ConfigType+  }+  deriving stock (Eq)++instance Default ApiConfig where+  def =+    ApiConfig+      { acApi = ApiUnspecified+      , acConfigType = ConfigTypeObserver+      }++mkApiConfig :: ApiConfig -> IO (ForeignPtr LMF.ApiConfig)+mkApiConfig ac = do+  f <- mallocPtr (Proxy @LMF.ApiConfig)+  withForeignPtr f $ \p -> do+    pokeField LMF.acApi p (toBitEnum (acApi ac))+    pokeField LMF.acConfigType p (toBitEnum (acConfigType ac))+  pure f++type ObsHandle = ForeignPtr LMF.ObsHandle++newObsHandle :: Api -> ObsConfig -> ErrM ObsHandle+newObsHandle api oc = do+  fac <- liftIO (mkApiConfig (ApiConfig {acApi = api, acConfigType = ConfigTypeObserver}))+  foc <- liftIO (mkObsConfig oc)+  let pac = unsafeForeignPtrToPtr fac+      poc = unsafeForeignPtrToPtr foc+  fh <- takeM (LMF.libremidi_midi_observer_new poc pac)+  liftIO $ do+    FC.addForeignPtrFinalizer fh (finalizeForeignPtr fac)+    FC.addForeignPtrFinalizer fh (finalizeForeignPtr foc)+  pure fh++freeObsHandle :: ObsHandle -> IO ()+freeObsHandle = finalizeForeignPtr++enumInPorts :: ObsHandle -> EnumInFun -> ErrM ()+enumInPorts h f = unRunErrM $ do+  cb <- newEnumCb f+  withForeignPtr h $ \p ->+    withCb cb $ \fun ->+      checkM (LMF.libremidi_midi_observer_enumerate_input_ports p nullPtr fun)++enumOutPorts :: ObsHandle -> EnumOutFun -> ErrM ()+enumOutPorts h f = unRunErrM $ do+  cb <- newEnumCb f+  withForeignPtr h $ \p ->+    withCb cb $ \fun ->+      checkM (LMF.libremidi_midi_observer_enumerate_output_ports p nullPtr fun)++type InHandle = ForeignPtr LMF.InHandle++newInHandle :: Api -> MidiConfig -> ErrM InHandle+newInHandle api mc = do+  fac <- liftIO (mkApiConfig (ApiConfig {acApi = api, acConfigType = ConfigTypeInput}))+  fmc <- liftIO (mkMidiConfig mc)+  let pac = unsafeForeignPtrToPtr fac+      pmc = unsafeForeignPtrToPtr fmc+  fh <- takeM (LMF.libremidi_midi_in_new pmc pac)+  liftIO $ do+    FC.addForeignPtrFinalizer fh (finalizeForeignPtr fac)+    FC.addForeignPtrFinalizer fh (finalizeForeignPtr fmc)+  pure fh++freeInHandle :: InHandle -> IO ()+freeInHandle = finalizeForeignPtr++inIsConnected :: InHandle -> IO Bool+inIsConnected fih = withForeignPtr fih (fmap fromCBool . LMF.libremidi_midi_in_is_connected)++inAbsTimestamp :: InHandle -> IO Timestamp+inAbsTimestamp fih = withForeignPtr fih (fmap fromCLong . LMF.libremidi_midi_in_absolute_timestamp)++type OutHandle = ForeignPtr LMF.OutHandle++newOutHandle :: Api -> MidiConfig -> ErrM OutHandle+newOutHandle api mc = do+  fac <- liftIO (mkApiConfig (ApiConfig {acApi = api, acConfigType = ConfigTypeOutput}))+  fmc <- liftIO (mkMidiConfig mc)+  let pac = unsafeForeignPtrToPtr fac+      pmc = unsafeForeignPtrToPtr fmc+  fh <- takeM (LMF.libremidi_midi_out_new pmc pac)+  liftIO $ do+    FC.addForeignPtrFinalizer fh (finalizeForeignPtr fac)+    FC.addForeignPtrFinalizer fh (finalizeForeignPtr fmc)+  pure fh++freeOutHandle :: OutHandle -> IO ()+freeOutHandle = finalizeForeignPtr++outIsConnected :: OutHandle -> IO Bool+outIsConnected foh = withForeignPtr foh (fmap fromCBool . LMF.libremidi_midi_out_is_connected)++type Msg1 = Ptr LMF.Sym1++outSendMsg1 :: OutHandle -> Msg1 -> Int -> ErrM ()+outSendMsg1 h msg len = unRunErrM $+  withForeignPtr h $ \p ->+    checkM (LMF.libremidi_midi_out_send_message p msg (toCSize (fromIntegral len)))++outSchedMsg1 :: OutHandle -> Timestamp -> Msg1 -> Int -> ErrM ()+outSchedMsg1 h ts msg len = unRunErrM $+  withForeignPtr h $ \p ->+    checkM (LMF.libremidi_midi_out_schedule_message p (toCLong ts) msg (toCSize (fromIntegral len)))++type Msg2 = Ptr LMF.Sym2++outSendMsg2 :: OutHandle -> Msg2 -> Int -> ErrM ()+outSendMsg2 h msg len = unRunErrM $+  withForeignPtr h $ \p ->+    checkM (LMF.libremidi_midi_out_send_ump p msg (toCSize (fromIntegral len)))++outSchedMsg2 :: OutHandle -> Timestamp -> Msg2 -> Int -> ErrM ()+outSchedMsg2 h ts msg len = unRunErrM $+  withForeignPtr h $ \p ->+    checkM (LMF.libremidi_midi_out_schedule_ump p (toCLong ts) msg (toCSize (fromIntegral len)))
+ src/Libremidi/Common.hs view
@@ -0,0 +1,236 @@+module Libremidi.Common where++import Control.Concurrent.STM (STM, atomically, retry, throwSTM)+import Control.Concurrent.STM.TVar (TVar, newTVarIO, readTVar, writeTVar)+import Control.Exception (Exception, finally, mask, mask_, throwIO)+import Control.Monad.Except (ExceptT (..), MonadError (..), runExceptT)+import Control.Monad.IO.Class (MonadIO (..))+import Data.Coerce (coerce)+import Data.Foldable (traverse_)+import Data.Int (Int32, Int64)+import Data.Kind (Type)+import Data.Proxy (Proxy)+import Data.Text (Text)+import Data.Text.Foreign qualified as TF+import Data.Word (Word64)+import Foreign.C.String (CString)+import Foreign.C.Types (CBool (..), CInt (..), CLong (..), CSize (..))+import Foreign.Concurrent qualified as FC+import Foreign.ForeignPtr (ForeignPtr, finalizeForeignPtr, mallocForeignPtrBytes, withForeignPtr)+import Foreign.Marshal.Alloc (alloca, free)+import Foreign.Marshal.Utils (fillBytes)+import Foreign.Ptr (FunPtr, Ptr, castFunPtrToPtr, castPtrToFunPtr, freeHaskellFunPtr, nullPtr, plusPtr)+import Foreign.Storable (Storable (..))++mallocForeignPtrBytes0 :: Int -> IO (ForeignPtr a)+mallocForeignPtrBytes0 len = do+  fp <- mallocForeignPtrBytes len+  withForeignPtr fp (\p -> fillBytes p 0 len)+  pure fp++allocaPtr :: (Ptr (Ptr x) -> IO a) -> IO a+allocaPtr f = alloca (\p -> poke p nullPtr >> f p)++ptrSize :: Int+ptrSize = sizeOf nullPtr++-- | A typed "accessor"+newtype Field a b = Field (Ptr a -> Ptr b)++newField :: Int -> Field a b+newField i = Field (`plusPtr` i)++pokeField :: (Storable b) => Field a b -> Ptr a -> b -> IO ()+pokeField (Field f) = poke . f++-- | Maps Haskell enums to C enums+class (Integral a, Enum b, Bounded b) => BitEnum a b | b -> a where+  fromBitEnum :: a -> Maybe b+  toBitEnum :: b -> a++-- | Generalizes ForeignPtr+class AssocPtr (z :: Type) where+  type PtrAssoc z :: Type+  withAssocPtr :: z -> (PtrAssoc z -> IO a) -> IO a++instance AssocPtr (ForeignPtr x) where+  type PtrAssoc (ForeignPtr x) = Ptr x+  withAssocPtr = withForeignPtr++data Ref p+  = RefUnlock !(ForeignPtr p)+  | RefLock+  | RefFree+  deriving stock (Eq, Ord, Show)++newtype UniquePtr p = UniquePtr+  { unUniquePtr :: TVar (Ref p)+  }+  deriving stock (Eq)++instance AssocPtr (UniquePtr x) where+  type PtrAssoc (UniquePtr x) = Ptr x+  withAssocPtr = withUniquePtr++data FreeErr = FreeErr+  deriving stock (Eq, Ord, Show)++instance Exception FreeErr++newUniquePtr :: ForeignPtr p -> IO (UniquePtr p)+newUniquePtr = fmap UniquePtr . newTVarIO . RefUnlock++aliveUniquePtr :: UniquePtr p -> STM Bool+aliveUniquePtr (UniquePtr v) = fmap isAlive (readTVar v)+ where+  isAlive = \case+    RefFree -> False+    _ -> True++freeUniquePtr :: UniquePtr p -> IO ()+freeUniquePtr (UniquePtr v) = mask_ $ do+  mfp <- atomically $ do+    r <- readTVar v+    case r of+      RefUnlock fp -> do+        writeTVar v RefFree+        pure (Just fp)+      RefLock -> retry+      RefFree -> pure Nothing+  traverse_ finalizeForeignPtr mfp++withUniquePtr' :: UniquePtr p -> (ForeignPtr p -> IO a) -> IO a+withUniquePtr' (UniquePtr v) f = mask $ \restore -> do+  fp <- atomically $ do+    r <- readTVar v+    case r of+      RefUnlock fp -> do+        writeTVar v RefLock+        pure fp+      RefLock -> retry+      RefFree -> throwSTM FreeErr+  finally+    (restore (f fp))+    (atomically (writeTVar v (RefUnlock fp)))++withUniquePtr :: UniquePtr p -> (Ptr p -> IO a) -> IO a+withUniquePtr u f = withUniquePtr' u (`withForeignPtr` f)++consumeUniquePtr :: UniquePtr p -> IO (ForeignPtr p)+consumeUniquePtr (UniquePtr v) = atomically $ do+  r <- readTVar v+  case r of+    RefUnlock fp -> do+      writeTVar v RefFree+      pure fp+    RefLock -> retry+    RefFree -> throwSTM FreeErr++-- | Like a ForeignFunPtr+-- We need to free the function pointer on finalization to not leak it+newtype Cb x = Cb {unCb :: UniquePtr x}+  deriving stock (Eq)++instance AssocPtr (Cb x) where+  type PtrAssoc (Cb x) = FunPtr x+  withAssocPtr = withCb++-- | Given an FFI wrapper function, allocate a callback+newCb :: (x -> IO (FunPtr x)) -> x -> IO (Cb x)+newCb w x = do+  y <- w x+  fp <- FC.newForeignPtr (castFunPtrToPtr y) (freeHaskellFunPtr y)+  up <- newUniquePtr fp+  pure (Cb up)++freeCb :: Cb x -> IO ()+freeCb = freeUniquePtr . unCb++withCb :: Cb x -> (FunPtr x -> IO a) -> IO a+withCb (Cb up) f = withUniquePtr up (f . castPtrToFunPtr)++consumeCb :: Cb x -> IO (ForeignPtr x)+consumeCb = consumeUniquePtr . unCb++-- | Foreign types that have a meaningful initial state+class MallocPtr (p :: Type) where+  mallocPtr :: Proxy p -> IO (ForeignPtr p)++-- | Error code returned by API calls+newtype Err = Err CInt+  deriving stock (Eq, Ord, Show)++instance Exception Err++-- | Computations that short-circuit on error+newtype ErrM a = ErrM {unErrM :: ExceptT Err IO a}+  deriving newtype (Functor, Applicative, Monad, MonadIO, MonadError Err)++runErrM :: ErrM a -> IO (Either Err a)+runErrM = runExceptT . unErrM++unRunErrM :: IO (Either Err a) -> ErrM a+unRunErrM = ErrM . ExceptT++rethrowErrM :: ErrM a -> IO a+rethrowErrM m = runErrM m >>= either throwIO pure++assocM :: (AssocPtr x) => (PtrAssoc x -> ErrM y) -> x -> ErrM y+assocM f fp = unRunErrM (withAssocPtr fp (runErrM . f))++checkM :: IO Err -> IO (Either Err ())+checkM one = do+  e@(Err x) <- liftIO one+  pure (if x == 0 then Right () else Left e)++checkAndThenM :: IO Err -> IO a -> IO (Either Err a)+checkAndThenM one two = do+  e@(Err x) <- liftIO one+  if x == 0+    then fmap Right two+    else pure (Left e)++textM :: (Ptr CString -> Ptr CSize -> IO Err) -> ErrM Text+textM f = unRunErrM $+  allocaPtr $ \sptr -> do+    alloca $ \lptr -> do+      checkAndThenM (f sptr lptr) $ do+        s <- peek sptr+        l <- peek lptr+        TF.fromPtr (coerce s) (fromIntegral l)++takeM :: (Ptr (Ptr x) -> IO Err) -> ErrM (ForeignPtr x)+takeM f = unRunErrM $+  allocaPtr $ \pptr -> do+    checkAndThenM (f pptr) $ do+      ptr <- peek pptr+      -- Cannot use finalizerFree because may have other haskell finalizers+      FC.newForeignPtr ptr (free ptr)++toCBool :: Bool -> CBool+toCBool = \case+  False -> CBool 0+  True -> CBool 1++fromCBool :: CBool -> Bool+fromCBool = \case+  0 -> False+  _ -> True++fromCLong :: CLong -> Int64+fromCLong (CLong x) = x++toCLong :: Int64 -> CLong+toCLong = CLong++fromCInt :: CInt -> Int32+fromCInt (CInt x) = x++toCInt :: Int32 -> CInt+toCInt = CInt++fromCSize :: CSize -> Word64+fromCSize (CSize x) = x++toCSize :: Word64 -> CSize+toCSize = CSize
+ src/Libremidi/Foreign.hsc view
@@ -0,0 +1,253 @@+{-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE UndecidableInstances #-}++-- | Low-level bindings to libremidi.+module Libremidi.Foreign where++import Foreign.C (CInt (..), CSize (..), CBool (..), CUInt (..), CLong (..), CUChar (..))+import Foreign.C.String (CString)+import Data.Void (Void)+import Foreign.Ptr (Ptr, FunPtr)+import Libremidi.Common (Err (..), Field, Cb, mallocForeignPtrBytes0, newCb, ptrSize, MallocPtr (..), newField)++#include "libremidi/libremidi-c.h"++#{enum TimestampMode+ , CInt+ , tmNoTimestamp = NoTimestamp+ , tmRelative = Relative+ , tmAbsolute = Absolute+ , tmSystemMonotonic = SystemMonotonic+ , tmAudioFrame = AudioFrame+ , tmCustom = Custom+}++type Api = CInt++#{enum Api+ , CInt+ , apiUnspecified = UNSPECIFIED+ , apiCoremidi = COREMIDI+ , apiAlsaSeq = ALSA_SEQ+ , apiAlsaRaw = ALSA_RAW+ , apiJackMidi = JACK_MIDI+ , apiWindowsMm = WINDOWS_MM+ , apiWindowsUwp = WINDOWS_UWP+ , apiWebmidi = WEBMIDI+ , apiPipewire = PIPEWIRE+ , apiAlsaRawUmp = ALSA_RAW_UMP+ , apiAlsaSeqUmp = ALSA_SEQ_UMP+ , apiCoremidiUmp = COREMIDI_UMP+ , apiWindowsMidiServices = WINDOWS_MIDI_SERVICES+ , apiDummy = DUMMY+}++type ConfigType = CInt++#{enum ConfigType+ , CInt+ , ctObserver = Observer+ , ctInput = Input+ , ctOutput = Output+}++type Version = CInt++#{enum Version+ , CInt+ , verMidi1 = MIDI1+ , verMidi2 = MIDI2+}++type Sym1 = CUChar+type Sym2 = CUInt++type Timestamp = CLong++data InPort+data OutPort++data InHandle+data OutHandle+data ObsHandle++type TimestampMode = CInt++data ApiConfig++instance MallocPtr ApiConfig where+  mallocPtr _ = mallocForeignPtrBytes0 #{size libremidi_api_configuration}++acApi :: Field ApiConfig Api+acApi = newField #{offset libremidi_api_configuration, api}++acConfigType :: Field ApiConfig ConfigType+acConfigType = newField #{offset libremidi_api_configuration, configuration_type}++acData :: Field ApiConfig (Ptr Void)+acData = newField #{offset libremidi_api_configuration, data}++type LogFun = Ptr Void -> CString -> CSize -> Ptr Void -> IO ()++foreign import ccall "wrapper"+  logWrap :: LogFun -> IO (FunPtr LogFun)++newLogCb :: LogFun -> IO (Cb LogFun)+newLogCb = newCb logWrap++type ObsFun p = Ptr Void -> Ptr p -> IO ()++foreign import ccall "wrapper"+  obsWrap :: ObsFun p -> IO (FunPtr (ObsFun p))++newObsCb :: ObsFun p -> IO (Cb (ObsFun p))+newObsCb = newCb obsWrap++data ObsConfig++instance MallocPtr ObsConfig where+  mallocPtr _ = mallocForeignPtrBytes0 #{size libremidi_observer_configuration}++ocOnErr :: Field ObsConfig (FunPtr LogFun)+ocOnErr = newField (ptrSize + #{offset libremidi_observer_configuration, on_error})++ocOnWarn :: Field ObsConfig (FunPtr LogFun)+ocOnWarn = newField (ptrSize + #{offset libremidi_observer_configuration, on_warning})++ocInAdd :: Field ObsConfig (FunPtr (ObsFun InPort))+ocInAdd = newField (ptrSize + #{offset libremidi_observer_configuration, input_added})++ocInRem :: Field ObsConfig (FunPtr (ObsFun InPort))+ocInRem = newField (ptrSize + #{offset libremidi_observer_configuration, input_removed})++ocOutAdd :: Field ObsConfig (FunPtr (ObsFun OutPort))+ocOutAdd = newField (ptrSize + #{offset libremidi_observer_configuration, input_added})++ocOutRem :: Field ObsConfig (FunPtr (ObsFun OutPort))+ocOutRem = newField (ptrSize + #{offset libremidi_observer_configuration, input_removed})++ocTrackHardware :: Field ObsConfig CBool+ocTrackHardware = newField #{offset libremidi_observer_configuration, track_hardware}++ocTrackVirtual :: Field ObsConfig CBool+ocTrackVirtual = newField #{offset libremidi_observer_configuration, track_virtual}++ocTrackAny :: Field ObsConfig CBool+ocTrackAny = newField #{offset libremidi_observer_configuration, track_any}++ocNotifInCon :: Field ObsConfig CBool+ocNotifInCon = newField #{offset libremidi_observer_configuration, notify_in_constructor}++type MsgFun d = Ptr Void -> Ptr d -> CSize -> IO ()++foreign import ccall "wrapper"+  msgWrap :: MsgFun d -> IO (FunPtr (MsgFun d))++newMsgCb :: MsgFun d -> IO (Cb (MsgFun d))+newMsgCb = newCb msgWrap++type TimeFun = Ptr Void -> Timestamp -> IO ()++foreign import ccall "wrapper"+  timeWrap :: TimeFun -> IO (FunPtr TimeFun)++newTimeCb :: TimeFun -> IO (Cb TimeFun)+newTimeCb = newCb timeWrap++data MidiConfig++instance MallocPtr MidiConfig where+  mallocPtr _ = mallocForeignPtrBytes0 #{size libremidi_midi_configuration}++mcVersion :: Field MidiConfig Version+mcVersion = newField #{offset libremidi_midi_configuration, version}++mcInPort :: Field MidiConfig (Ptr InPort)+mcInPort = newField #{offset libremidi_midi_configuration, in_port}++mcOutPort :: Field MidiConfig (Ptr OutPort)+mcOutPort = newField #{offset libremidi_midi_configuration, out_port}++mcOnMsg1 :: Field MidiConfig (FunPtr (MsgFun Sym1))+mcOnMsg1 = newField #{offset libremidi_midi_configuration, on_midi1_message}++mcOnMsg2 :: Field MidiConfig (FunPtr (MsgFun Sym2))+mcOnMsg2 = newField #{offset libremidi_midi_configuration, on_midi2_message}++mcGetTime :: Field MidiConfig (FunPtr TimeFun)+mcGetTime = newField #{offset libremidi_midi_configuration, get_timestamp}++mcOnErr :: Field MidiConfig (FunPtr LogFun)+mcOnErr = newField #{offset libremidi_midi_configuration, on_error}++mcOnWarn :: Field MidiConfig (FunPtr LogFun)+mcOnWarn = newField #{offset libremidi_midi_configuration, on_warning}++mcPortName :: Field MidiConfig CString+mcPortName = newField #{offset libremidi_midi_configuration, port_name}++mcVirtualPort :: Field MidiConfig CBool+mcVirtualPort = newField #{offset libremidi_midi_configuration, virtual_port}++mcIgnoreSysex :: Field MidiConfig CBool+mcIgnoreSysex = newField #{offset libremidi_midi_configuration, ignore_sysex}++mcIgnoreTiming :: Field MidiConfig CBool+mcIgnoreTiming = newField #{offset libremidi_midi_configuration, ignore_timing}++mcIgnoreSensing :: Field MidiConfig CBool+mcIgnoreSensing = newField #{offset libremidi_midi_configuration, ignore_sensing}++mcTimestamps :: Field MidiConfig TimestampMode+mcTimestamps = newField #{offset libremidi_midi_configuration, timestamps}++foreign import ccall "libremidi/libremidi-c.h"+  libremidi_midi_in_port_clone :: Ptr InPort -> Ptr (Ptr InPort) -> IO Err++foreign import ccall "libremidi/libremidi-c.h"+  libremidi_midi_in_port_name :: Ptr InPort -> Ptr CString -> Ptr CSize -> IO Err++foreign import ccall "libremidi/libremidi-c.h"+  libremidi_midi_out_port_clone :: Ptr OutPort -> Ptr (Ptr OutPort) -> IO Err++foreign import ccall "libremidi/libremidi-c.h"+  libremidi_midi_out_port_name :: Ptr OutPort -> Ptr CString -> Ptr CSize -> IO Err++foreign import ccall "libremidi/libremidi-c.h"+  libremidi_midi_observer_new :: Ptr ObsConfig -> Ptr ApiConfig -> Ptr (Ptr ObsHandle) -> IO Err++foreign import ccall "libremidi/libremidi-c.h"+  libremidi_midi_observer_enumerate_input_ports :: Ptr ObsHandle -> Ptr Void -> FunPtr (ObsFun InPort) -> IO Err++foreign import ccall "libremidi/libremidi-c.h"+  libremidi_midi_observer_enumerate_output_ports :: Ptr ObsHandle -> Ptr Void -> FunPtr (ObsFun OutPort) -> IO Err++foreign import ccall "libremidi/libremidi-c.h"+  libremidi_midi_in_new :: Ptr MidiConfig -> Ptr ApiConfig -> Ptr (Ptr InHandle) -> IO Err++foreign import ccall "libremidi/libremidi-c.h"+  libremidi_midi_in_is_connected :: Ptr InHandle -> IO CBool++foreign import ccall "libremidi/libremidi-c.h"+  libremidi_midi_in_absolute_timestamp :: Ptr InHandle -> IO Timestamp++foreign import ccall "libremidi/libremidi-c.h"+  libremidi_midi_in_free :: Ptr InHandle -> IO Err++foreign import ccall "libremidi/libremidi-c.h"+  libremidi_midi_out_new :: Ptr MidiConfig -> Ptr ApiConfig -> Ptr (Ptr OutHandle) -> IO Err++foreign import ccall "libremidi/libremidi-c.h"+  libremidi_midi_out_is_connected :: Ptr OutHandle -> IO CBool++foreign import ccall "libremidi/libremidi-c.h"+  libremidi_midi_out_send_message :: Ptr OutHandle -> Ptr Sym1 -> CSize -> IO Err++foreign import ccall "libremidi/libremidi-c.h"+  libremidi_midi_out_send_ump ::  Ptr OutHandle -> Ptr Sym2 -> CSize -> IO Err++foreign import ccall "libremidi/libremidi-c.h"+  libremidi_midi_out_schedule_message :: Ptr OutHandle -> Timestamp -> Ptr Sym1 -> CSize  -> IO Err++foreign import ccall "libremidi/libremidi-c.h"+  libremidi_midi_out_schedule_ump :: Ptr OutHandle -> Timestamp -> Ptr Sym2 -> CSize -> IO Err
+ src/Libremidi/Simple.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE OverloadedStrings #-}++-- | High-level operations.+module Libremidi.Simple where++import Control.Monad (when)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Reader (ReaderT (..), ask)+import Data.Default (def)+import Data.IORef (IORef, modifyIORef', newIORef, readIORef, writeIORef)+import Data.Maybe (isNothing)+import Data.Sequence (Seq (..))+import Data.Text (Text)+import Data.Text.IO qualified as TIO+import Foreign.ForeignPtr (ForeignPtr)+import Foreign.Ptr (Ptr)+import Libremidi.Api qualified as LMA+import Libremidi.Common (ErrM, newUniquePtr, rethrowErrM)+import System.IO qualified as SIO++type MidiM = ReaderT LMA.LogFun IO++runMidiM :: MidiM a -> LMA.LogFun -> IO a+runMidiM = runReaderT++nullLogFun :: LMA.LogFun+nullLogFun _ _ = pure ()++stderrLogFun :: LMA.LogFun+stderrLogFun lvl msg =+  let prefix = case lvl of+        LMA.LogLvlWarn -> "[WARN] "+        LMA.LogLvlErr -> "[ERR ] "+  in  TIO.hPutStrLn SIO.stderr (prefix <> msg)++newObsHandle :: MidiM LMA.ObsHandle+newObsHandle = do+  lf <- ask+  liftIO $ do+    oc <- LMA.setObsLogCb lf def+    rethrowErrM (LMA.newObsHandle LMA.ApiUnspecified oc)++listEnumFun+  :: (Ptr p -> ErrM Text)+  -> (Ptr p -> ErrM (ForeignPtr p))+  -> IORef (Seq (Text, ForeignPtr p))+  -> LMA.EnumFun p+listEnumFun name clone r p = do+  n <- rethrowErrM (name p)+  fp <- rethrowErrM (clone p)+  modifyIORef' r (:|> (n, fp))++listPorts+  :: (Ptr p -> ErrM Text)+  -> (Ptr p -> ErrM (ForeignPtr p))+  -> (LMA.ObsHandle -> LMA.EnumFun p -> ErrM ())+  -> MidiM (Seq (Text, ForeignPtr p))+listPorts name clone list = do+  r <- liftIO (newIORef Empty)+  let f = listEnumFun name clone r+  foh <- newObsHandle+  liftIO $ do+    rethrowErrM (list foh f)+    readIORef r++listInPorts :: MidiM (Seq (Text, LMA.InPort))+listInPorts = listPorts LMA.inPortName' LMA.cloneInPort' LMA.enumInPorts++listOutPorts :: MidiM (Seq (Text, LMA.OutPort))+listOutPorts = listPorts LMA.outPortName' LMA.cloneOutPort' LMA.enumOutPorts++findEnumFun+  :: (Ptr p -> ErrM Text)+  -> (Ptr p -> ErrM (ForeignPtr p))+  -> (Text -> Bool)+  -> IORef (Maybe (Text, ForeignPtr p))+  -> LMA.EnumFun p+findEnumFun name clone sel r p = do+  missing <- fmap isNothing (readIORef r)+  when missing $ do+    n <- rethrowErrM (name p)+    when (sel n) $ do+      fp <- rethrowErrM (clone p)+      writeIORef r (Just (n, fp))++findPort+  :: (Ptr p -> ErrM Text)+  -> (Ptr p -> ErrM (ForeignPtr p))+  -> (LMA.ObsHandle -> LMA.EnumFun p -> ErrM ())+  -> (Text -> Bool)+  -> MidiM (Maybe (Text, ForeignPtr p))+findPort name clone list sel = do+  r <- liftIO (newIORef Nothing)+  let f = findEnumFun name clone sel r+  foh <- newObsHandle+  liftIO $ do+    rethrowErrM (list foh f)+    readIORef r++findInPort :: (Text -> Bool) -> MidiM (Maybe (Text, LMA.InPort))+findInPort = findPort LMA.inPortName' LMA.cloneInPort' LMA.enumInPorts++findOutPort :: (Text -> Bool) -> MidiM (Maybe (Text, LMA.OutPort))+findOutPort = findPort LMA.outPortName' LMA.cloneOutPort' LMA.enumOutPorts++openOutPort :: LMA.OutPort -> MidiM LMA.OutHandle+openOutPort op = do+  lf <- ask+  liftIO $ rethrowErrM $ do+    op' <- LMA.cloneOutPort op >>= liftIO . newUniquePtr+    let mc = def {LMA.mcPort = Just (LMA.MidiPortOut op')}+    mc' <- liftIO (LMA.setMidiLogCb lf mc)+    LMA.newOutHandle LMA.ApiUnspecified mc'
+ test/Main.hs view
@@ -0,0 +1,21 @@+module Main+  ( main+  )+where++import Test.Tasty (TestTree, defaultMain, testGroup)+import Test.Tasty.HUnit (testCase, (@?=))++testDummy :: TestTree+testDummy = testCase "dummy" $ do+  let actual = (1 + 1) :: Int+      expected = 2 :: Int+  actual @?= expected++main :: IO ()+main =+  defaultMain $+    testGroup+      "Libremidi"+      [ testDummy+      ]
+ vendor/libremidi/client.cpp view
@@ -0,0 +1,47 @@+#if !defined(LIBREMIDI_HEADER_ONLY)+  #include "client.hpp"+#endif+#include <libremidi/backends.hpp>+#include <libremidi/shared_context.hpp>++#ifdef LIBREMIDI_ALSA+  #include <libremidi/backends/alsa_seq/shared_handler.hpp>+#endif+#ifdef LIBREMIDI_JACK+  #include <libremidi/backends/jack/shared_handler.hpp>+#endif+namespace libremidi+{+LIBREMIDI_INLINE+shared_configurations+create_shared_context(const libremidi::API api, [[maybe_unused]] std::string_view client_name)+{+  switch (api)+  {+#if __has_include(<boost/lockfree/spsc_queue.hpp>)+  #if defined(LIBREMIDI_ALSA)+    case libremidi::API::ALSA_SEQ:+      return alsa_seq::shared_handler::make(client_name);+  #endif++  #if defined(LIBREMIDI_JACK)+    case libremidi::API::JACK_MIDI:+      return jack::shared_handler::make(client_name);+  #endif+#endif+    case libremidi::API::COREMIDI:+    case libremidi::API::COREMIDI_UMP:+      // TODO++    case libremidi::API::WINDOWS_MIDI_SERVICES:+      // TODO++    default:+      return {+          .context = {},+          .observer = observer_configuration_for(api),+          .in = midi_in_configuration_for(api),+          .out = midi_out_configuration_for(api)};+  }+}+}
+ vendor/libremidi/libremidi-c.cpp view
@@ -0,0 +1,507 @@+// clang-format off+#include <libremidi/libremidi-c.h>+#include <libremidi/libremidi.hpp>+#include <libremidi/backends.hpp>+// clang-format on++#include <cerrno>+#include <cstdlib>+#include <cstring>+#include <limits>++struct libremidi_midi_observer_handle+{+  libremidi::observer self;+};++struct libremidi_midi_in_handle+{+  libremidi::midi_in self;+};++struct libremidi_midi_out_handle+{+  libremidi::midi_out self;+};++namespace libremidi+{++static void assign_error_callback(const auto& src, auto& dst)+{+  if (src.callback)+  {+    dst = [src](std::string_view errorText, const source_location& loc) {+      src.callback(src.context, errorText.data(), errorText.size(), &loc);+    };+  }+}+}++extern "C" {+const char* libremidi_get_version(void)+{+  return LIBREMIDI_VERSION;+}++void libremidi_available_midi1_apis(void* ctx, void (*cb)(void* ctx, libremidi_api))+{+  if (!cb)+    return;+  libremidi::midi1::for_all_backends([=](auto b) { cb(ctx, b.API); });+}++void libremidi_available_midi2_apis(void* ctx, void (*cb)(void* ctx, libremidi_api))+{+  if (!cb)+    return;+  libremidi::midi2::for_all_backends([=](auto b) { cb(ctx, b.API); });+}++const char* libremidi_api_identifier(libremidi_api api)+{+  return libremidi::get_api_name(api).data();+}++const char* libremidi_api_display_name(libremidi_api api)+{+  return libremidi::get_api_display_name(api).data();+}++libremidi_api libremidi_get_compiled_api_by_identifier(const char* name)+{+  libremidi_api ret = libremidi_api::UNSPECIFIED;+  libremidi::midi_any::for_all_backends([&](auto& b) {+    if (name == b.name)+      ret = b.API;+  });+  return ret;+}++int libremidi_midi_api_configuration_init(libremidi_api_configuration* conf)+{+  memset(conf, 0, sizeof(*conf));+  return 0;+}++int libremidi_midi_observer_configuration_init(libremidi_observer_configuration* conf)+{+  memset(conf, 0, sizeof(*conf));+  return 0;+}++int libremidi_midi_configuration_init(libremidi_midi_configuration* conf)+{+  memset(conf, 0, sizeof(*conf));+  return 0;+}++int libremidi_midi_in_port_clone(const libremidi_midi_in_port* port, libremidi_midi_in_port** dst)+{+  if (!port || !dst)+    return -EINVAL;++  auto copied = new libremidi::input_port{*reinterpret_cast<const libremidi::input_port*>(port)};+  *dst = reinterpret_cast<libremidi_midi_in_port*>(copied);+  return 0;+}++int libremidi_midi_in_port_free(libremidi_midi_in_port* port)+{+  delete reinterpret_cast<libremidi::input_port*>(port);+  return 0;+}++int libremidi_midi_in_port_name(const libremidi_midi_in_port* port, const char** name, size_t* len)+{+  if (!port || !name || !len)+    return -EINVAL;++  auto& p = *reinterpret_cast<const libremidi::input_port*>(port);+  *name = p.port_name.data();+  *len = p.port_name.size();+  return 0;+}++int libremidi_midi_out_port_clone(+    const libremidi_midi_out_port* port, libremidi_midi_out_port** dst)+{+  if (!port || !dst)+    return -EINVAL;++  auto copied = new libremidi::output_port{*reinterpret_cast<const libremidi::output_port*>(port)};+  *dst = reinterpret_cast<libremidi_midi_out_port*>(copied);+  return 0;+}++int libremidi_midi_out_port_free(libremidi_midi_out_port* port)+{+  delete reinterpret_cast<libremidi::output_port*>(port);+  return 0;+}++int libremidi_midi_out_port_name(+    const libremidi_midi_out_port* port, const char** name, size_t* len)+{+  if (!port || !name || !len)+    return -EINVAL;++  auto& p = *reinterpret_cast<const libremidi::output_port*>(port);+  *name = p.port_name.data();+  *len = p.port_name.size();+  return 0;+}++int libremidi_midi_observer_new(+    const libremidi_observer_configuration* c, libremidi_api_configuration* api,+    libremidi_midi_observer_handle** out)+{+  if (!out || !c)+    return -EINVAL;++  libremidi::observer_configuration conf;+  libremidi::assign_error_callback(c->on_error, conf.on_error);+  libremidi::assign_error_callback(c->on_warning, conf.on_warning);++  conf.track_hardware = c->track_hardware;+  conf.track_virtual = c->track_virtual;+  conf.track_any = c->track_any;+  conf.notify_in_constructor = c->notify_in_constructor;++  auto api_conf = libremidi::observer_configuration_for(static_cast<libremidi::API>(api->api));++  if (c->input_added.callback)+  {+    conf.input_added = [cb = c->input_added](const auto& port) {+      cb.callback(cb.context, reinterpret_cast<const libremidi_midi_in_port*>(&port));+    };+  }+  if (c->input_removed.callback)+  {+    conf.input_removed = [cb = c->input_removed](const auto& port) {+      cb.callback(cb.context, reinterpret_cast<const libremidi_midi_in_port*>(&port));+    };+  }+  if (c->output_added.callback)+  {+    conf.output_added = [cb = c->output_added](const auto& port) {+      cb.callback(cb.context, reinterpret_cast<const libremidi_midi_out_port*>(&port));+    };+  }+  if (c->output_removed.callback)+  {+    conf.output_removed = [cb = c->output_removed](const auto& port) {+      cb.callback(cb.context, reinterpret_cast<const libremidi_midi_out_port*>(&port));+    };+  }++  try+  {+    auto ptr = new libremidi_midi_observer_handle{+        libremidi::observer{std::move(conf), std::move(api_conf)}};+    *out = ptr;+    return 0;+  }+  catch (...)+  {+    return -EINVAL;+  }+}++int libremidi_midi_observer_enumerate_input_ports(+    libremidi_midi_observer_handle* ptr, void* context,+    void (*cb)(void* ctx, const libremidi_midi_in_port*))+{+  if (!ptr || !cb)+    return -EINVAL;++  for (const auto& port : ptr->self.get_input_ports())+  {+    cb(context, reinterpret_cast<const libremidi_midi_in_port*>(&port));+  }++  return 0;+}++int libremidi_midi_observer_enumerate_output_ports(+    libremidi_midi_observer_handle* ptr, void* context,+    void (*cb)(void* ctx, const libremidi_midi_out_port*))+{+  if (!ptr || !cb)+    return -EINVAL;++  for (const auto& port : ptr->self.get_output_ports())+  {+    cb(context, reinterpret_cast<const libremidi_midi_out_port*>(&port));+  }++  return 0;+}++int libremidi_midi_observer_free(libremidi_midi_observer_handle* ptr)+{+  delete ptr;+  return 0;+}++int libremidi_midi_in_new(+    const libremidi_midi_configuration* c, const libremidi_api_configuration* api,+    libremidi_midi_in_handle** out)+{+  if (!out || !c)+    return -EINVAL;+  if (!c->virtual_port && !c->in_port)+    return -EINVAL;++  *out = nullptr;++  auto api_conf = libremidi::midi_in_configuration_for(static_cast<libremidi::API>(api->api));++  // Create the MIDI object+  switch (c->version)+  {+    case libremidi_midi_configuration::MIDI1:+    case libremidi_midi_configuration::MIDI1_RAW: {+      libremidi::input_configuration conf;+      libremidi::assign_error_callback(c->on_error, conf.on_error);+      libremidi::assign_error_callback(c->on_warning, conf.on_warning);+      conf.ignore_sensing = c->ignore_sensing;+      conf.ignore_sysex = c->ignore_sysex;+      conf.ignore_timing = c->ignore_timing;+      conf.timestamps = c->timestamps;++      if (c->get_timestamp.callback)+      {+        conf.get_timestamp+            = [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); };+      }++      if (c->on_midi1_message.callback)+        conf.on_message = [cb = c->on_midi1_message](const libremidi::message& msg) {+          cb.callback(cb.context, msg.timestamp, msg.bytes.data(), msg.size());+        };+      else if (c->on_midi1_raw_data.callback)+      {+        conf.on_raw_data = [cb = c->on_midi1_raw_data](std::span<const uint8_t> msg, int64_t ts) {+          cb.callback(cb.context, ts, msg.data(), msg.size());+        };+      }+      else+      {+        return -EINVAL;+      }++      try+      {+        auto ptr = new libremidi_midi_in_handle{+            libremidi::midi_in{std::move(conf), std::move(api_conf)}};+        *out = ptr;+      }+      catch (...)+      {+        return -EINVAL;+      }+      break;+    }+    case libremidi_midi_configuration::MIDI2:+    case libremidi_midi_configuration::MIDI2_RAW: {+      libremidi::ump_input_configuration conf;+      libremidi::assign_error_callback(c->on_error, conf.on_error);+      libremidi::assign_error_callback(c->on_warning, conf.on_warning);+      conf.ignore_sensing = c->ignore_sensing;+      conf.ignore_sysex = c->ignore_sysex;+      conf.ignore_timing = c->ignore_timing;+      conf.timestamps = c->timestamps;++      if (c->get_timestamp.callback)+      {+        conf.get_timestamp+            = [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); };+      }++      if (c->on_midi2_message.callback)+        conf.on_message = [cb = c->on_midi2_message](const libremidi::ump& msg) {+          cb.callback(cb.context, msg.timestamp, msg.data, msg.size());+        };+      else if (c->on_midi2_raw_data.callback)+      {+        conf.on_raw_data = [cb = c->on_midi2_raw_data](std::span<const uint32_t> msg, int64_t ts) {+          cb.callback(cb.context, ts, msg.data(), msg.size());+        };+      }+      else+      {+        return -EINVAL;+      }++      try+      {+        auto ptr = new libremidi_midi_in_handle{+            libremidi::midi_in{std::move(conf), std::move(api_conf)}};+        *out = ptr;+      }+      catch (...)+      {+        return -EINVAL;+      }+      break;+    }+    default:+      return -EINVAL;+  }++  // Open the port+  auto ptr = *out;+  if (!ptr)+    return -EINVAL;++  if (c->virtual_port)+  {+    if (auto ret = ptr->self.open_virtual_port(c->port_name); ret != stdx::error{})+    {+      *out = nullptr;+      delete ptr;+      return -EIO;+    }+  }+  else+  {+    auto port = reinterpret_cast<const libremidi::input_port*>(c->in_port);+    if (auto ret = ptr->self.open_port(*port); ret != stdx::error{})+    {+      *out = nullptr;+      delete ptr;+      return -EIO;+    }+  }++  return 0;+}+int libremidi_midi_in_is_connected(const libremidi_midi_in_handle* in)+{+  if (!in)+    return -EINVAL;+  return in->self.is_port_connected() ? 1 : 0;+}++libremidi_timestamp libremidi_midi_in_absolute_timestamp(libremidi_midi_in_handle* in)+{+  if (!in)+    return -EINVAL;+  return in->self.absolute_timestamp();+}++int libremidi_midi_in_free(libremidi_midi_in_handle* ptr)+{+  delete ptr;+  return 0;+}++int libremidi_midi_out_new(+    const libremidi_midi_configuration* c, const libremidi_api_configuration* api,+    libremidi_midi_out_handle** out)+{+  if (!out || !c)+    return -EINVAL;+  if (!c->virtual_port && !c->out_port)+    return -EINVAL;++  *out = nullptr;++  auto api_conf = libremidi::midi_out_configuration_for(static_cast<libremidi::API>(api->api));++  // Create the MIDI object+  libremidi::output_configuration conf;+  libremidi::assign_error_callback(c->on_error, conf.on_error);+  libremidi::assign_error_callback(c->on_warning, conf.on_warning);+  conf.timestamps = c->timestamps;+  try+  {+    auto ptr+        = new libremidi_midi_out_handle{libremidi::midi_out{std::move(conf), std::move(api_conf)}};+    *out = ptr;+  }+  catch (...)+  {+    return -EINVAL;+  }++  // Open the port+  auto ptr = *out;+  if (!ptr)+    return -EINVAL;++  if (c->virtual_port)+  {+    if (auto ret = ptr->self.open_virtual_port(c->port_name); ret != stdx::error{})+    {+      *out = nullptr;+      delete ptr;+      return -EIO;+    }+  }+  else+  {+    auto port = reinterpret_cast<const libremidi::output_port*>(c->in_port);+    if (auto ret = ptr->self.open_port(*port); ret != stdx::error{})+    {+      *out = nullptr;+      delete ptr;+      return -EIO;+    }+  }+  return 0;+}++int libremidi_midi_out_is_connected(const libremidi_midi_out_handle* out)+{+  if (!out)+    return -EINVAL;+  return out->self.is_port_connected() ? 1 : 0;+}++int libremidi_midi_out_send_message(+    libremidi_midi_out_handle* out, const libremidi_midi1_symbol* msg, size_t sz)+{+  if (!out || !msg || sz > std::numeric_limits<int32_t>::max())+    return -EINVAL;++  auto res = out->self.send_message(msg, sz);+  return res != stdx::error{} ? -EIO : 0;+}++int libremidi_midi_out_send_ump(libremidi_midi_out_handle* out, const libremidi_midi2_symbol* msg, size_t sz)+{+  if (!out || !msg || sz > std::numeric_limits<int32_t>::max())+    return -EINVAL;++  auto res = out->self.send_ump(msg, sz);+  return res != stdx::error{} ? -EIO : 0;+}++int libremidi_midi_out_schedule_message(+    libremidi_midi_out_handle* out, int64_t ts, const libremidi_midi1_symbol* msg, size_t sz)+{+  if (!out || !msg || sz > std::numeric_limits<int32_t>::max())+    return -EINVAL;++  auto res = out->self.schedule_message(ts, msg, sz);+  return res != stdx::error{} ? -EIO : 0;+}++int libremidi_midi_out_schedule_ump(+    libremidi_midi_out_handle* out, int64_t ts, const libremidi_midi2_symbol* msg, size_t sz)+{+  if (!out || !msg || sz > std::numeric_limits<int32_t>::max())+    return -EINVAL;++  auto res = out->self.schedule_ump(ts, msg, sz);+  return res != stdx::error{} ? -EIO : 0;+}++int libremidi_midi_out_free(libremidi_midi_out_handle* ptr)+{+  delete ptr;+  return 0;+}+}
+ vendor/libremidi/libremidi.cpp view
@@ -0,0 +1,186 @@+#if !defined(LIBREMIDI_HEADER_ONLY)+  #include <libremidi/libremidi.hpp>+#endif++#include <libremidi/backends.hpp>+#include <libremidi/detail/midi_api.hpp>++#include <cmath>++namespace libremidi+{++LIBREMIDI_INLINE+std::string_view get_version() noexcept+{+  return LIBREMIDI_VERSION;+}++LIBREMIDI_INLINE std::string_view get_api_name(libremidi::API api)+{+  std::string_view ret;+  midi_any::for_backend(api, [&](auto& b) { ret = b.name; });+  return ret;+}++LIBREMIDI_INLINE std::string_view get_api_display_name(libremidi::API api)+{+  std::string_view ret;+  midi_any::for_backend(api, [&](auto& b) { ret = b.display_name; });+  return ret;+}++LIBREMIDI_INLINE libremidi::API get_compiled_api_by_name(std::string_view name)+{+  libremidi::API ret = libremidi::API::UNSPECIFIED;+  midi_any::for_all_backends([&](auto& b) {+    if (name == b.name)+      ret = b.API;+  });+  return ret;+}++[[nodiscard]] LIBREMIDI_INLINE std::vector<libremidi::API> available_apis() noexcept+{+  std::vector<libremidi::API> apis;+  midi1::for_all_backends([&](auto b) { apis.push_back(b.API); });+  return apis;+}++[[nodiscard]] LIBREMIDI_INLINE std::vector<libremidi::API> available_ump_apis() noexcept+{+  std::vector<libremidi::API> apis;+  midi2::for_all_backends([&](auto b) { apis.push_back(b.API); });+  return apis;+}+LIBREMIDI_INLINE+libremidi::API midi_api(const std::any& conf)+{+  libremidi::API ret = libremidi::API::UNSPECIFIED;+  midi_any::for_all_backends([&]<typename T>(T) {+    if (std::any_cast<typename T::midi_in_configuration>(&conf)+        || std::any_cast<typename T::midi_out_configuration>(&conf)+        || std::any_cast<typename T::midi_observer_configuration>(&conf))+    {+      ret = T::API;+    }+  });+  return ret;+}+LIBREMIDI_INLINE+std::any midi_in_configuration_for(libremidi::API api)+{+  std::any ret;+  midi_any::for_backend(api, [&]<typename T>(T) {+    using conf_type = typename T::midi_in_configuration;+    ret = conf_type{};+  });+  return ret;+}++LIBREMIDI_INLINE+std::any midi_out_configuration_for(libremidi::API api)+{+  std::any ret;+  midi_any::for_backend(api, [&]<typename T>(T) {+    using conf_type = typename T::midi_out_configuration;+    ret = conf_type{};+  });+  return ret;+}++LIBREMIDI_INLINE+std::any observer_configuration_for(libremidi::API api)+{+  std::any ret;+  midi_any::for_backend(api, [&]<typename T>(T) {+    using conf_type = typename T::midi_observer_configuration;+    ret = conf_type{};+  });+  return ret;+}++LIBREMIDI_INLINE+std::any midi_in_configuration_for(const libremidi::observer& obs)+{+  return midi_in_configuration_for(obs.get_current_api());+}++LIBREMIDI_INLINE+std::any midi_out_configuration_for(const libremidi::observer& obs)+{+  // FIXME reuse context when meaningful, e.g. ALSA, JACK...+  return midi_out_configuration_for(obs.get_current_api());+}++LIBREMIDI_INLINE+std::optional<input_port> in_default_port(libremidi::API api) noexcept+try+{+  libremidi::observer obs{{}, observer_configuration_for(api)};+  if (auto ports = obs.get_input_ports(); !ports.empty())+    return ports.front();+  return std::nullopt;+}+catch (const std::exception& e)+{+  return std::nullopt;+}++LIBREMIDI_INLINE+std::optional<output_port> out_default_port(libremidi::API api) noexcept+try+{+  libremidi::observer obs{{}, observer_configuration_for(api)};+  if (auto ports = obs.get_output_ports(); !ports.empty())+    return ports.front();+  return std::nullopt;+}+catch (const std::exception& e)+{+  return std::nullopt;+}++namespace midi1+{+LIBREMIDI_INLINE+std::any in_default_configuration()+{+  return midi_in_configuration_for(default_api());+}++LIBREMIDI_INLINE+std::any out_default_configuration()+{+  return midi_out_configuration_for(default_api());+}++LIBREMIDI_INLINE+std::any observer_default_configuration()+{+  return observer_configuration_for(default_api());+}+}++namespace midi2+{+LIBREMIDI_INLINE+std::any in_default_configuration()+{+  return midi_in_configuration_for(default_api());+}++LIBREMIDI_INLINE+std::any out_default_configuration()+{+  return midi_out_configuration_for(default_api());+}++LIBREMIDI_INLINE+std::any observer_default_configuration()+{+  return observer_configuration_for(default_api());+}+}++}
+ vendor/libremidi/midi_in.cpp view
@@ -0,0 +1,373 @@+#if !defined(LIBREMIDI_HEADER_ONLY)+  #include <libremidi/libremidi.hpp>+#endif++#include <libremidi/backends.hpp>+#include <libremidi/detail/midi_api.hpp>++#include <cassert>++namespace libremidi+{++static libremidi::ump_input_configuration+convert_midi1_to_midi2_input_configuration(const input_configuration& base_conf) noexcept+{+  libremidi::ump_input_configuration c2;+  c2.on_message = [cb = base_conf.on_message,+                   converter = midi2_to_midi1{}](libremidi::ump&& msg) mutable -> void {+    converter.convert(+        msg.data, 1, msg.timestamp, [cb](const unsigned char* midi, std::size_t n, int64_t ts) {+      cb(libremidi::message{{midi, midi + n}, ts});+      return stdx::error{};+    });+  };+  c2.get_timestamp = base_conf.get_timestamp;+  c2.on_error = base_conf.on_error;+  c2.on_warning = base_conf.on_warning;+  c2.ignore_sysex = base_conf.ignore_sysex;+  c2.ignore_timing = base_conf.ignore_timing;+  c2.ignore_sensing = base_conf.ignore_sensing;+  c2.timestamps = base_conf.timestamps;+  return c2;+}++static libremidi::input_configuration+convert_midi2_to_midi1_input_configuration(const ump_input_configuration& base_conf) noexcept+{+  libremidi::input_configuration c2;+  c2.on_message = [cb = base_conf.on_message,+                   converter = midi1_to_midi2{}](libremidi::message&& msg) mutable -> void {+    converter.convert(+        msg.bytes.data(), msg.bytes.size(), msg.timestamp,+        [cb](const uint32_t* ump, std::size_t n, int64_t ts) {+      if(n >= 4)+      {+        libremidi::ump u{ump[0],ump[1],ump[2],ump[3]};+        u.timestamp = ts;+        cb(std::move(u));+      }+      return stdx::error{};+    });+  };+  c2.get_timestamp = base_conf.get_timestamp;+  c2.on_error = base_conf.on_error;+  c2.on_warning = base_conf.on_warning;+  c2.ignore_sysex = base_conf.ignore_sysex;+  c2.ignore_timing = base_conf.ignore_timing;+  c2.ignore_sensing = base_conf.ignore_sensing;+  c2.timestamps = base_conf.timestamps;+  return c2;+}++static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>+make_midi_in(auto base_conf, std::any api_conf, auto backends)+{+  std::unique_ptr<midi_in_api> ptr;++  assert(base_conf.on_message || base_conf.on_raw_data);++  auto from_api = [&]<typename T>(T& /*backend*/) mutable {+    if (auto conf = std::any_cast<typename T::midi_in_configuration>(&api_conf))+    {+      ptr = libremidi::make<typename T::midi_in>(std::move(base_conf), std::move(*conf));+      return true;+    }+    return false;+  };+  std::apply([&](auto&&... b) { (from_api(b) || ...); }, backends);+  return ptr;+}++/// MIDI 1 helpers+static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>+make_midi1_in(const input_configuration& base_conf)+{+  for (const auto& api : available_apis())+  {+    try+    {+      auto impl+          = make_midi_in(base_conf, midi_in_configuration_for(api), midi1::available_backends);+      if (impl)+        return impl;+    }+    catch (const std::exception& e)+    {+    }+  }++  // No MIDI 1 backend, try the MIDI 2 ones with a wrap:+  {+    auto c2 = convert_midi1_to_midi2_input_configuration(base_conf);+    for (const auto& api : available_ump_apis())+    {+      try+      {+        auto impl = make_midi_in(c2, midi_in_configuration_for(api), midi2::available_backends);+        if (impl)+          return impl;+      }+      catch (const std::exception& e)+      {+      }+    }+  }++  return std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});+}++static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>+make_midi1_in(const input_configuration& base_conf, const std::any& api_conf, libremidi::API api)+{+  if (libremidi::is_midi1(api))+  {+    return make_midi_in(base_conf, api_conf, midi1::available_backends);+  }+  else if (libremidi::is_midi2(api))+  {+    auto c2 = convert_midi1_to_midi2_input_configuration(base_conf);+    return make_midi_in(c2, api_conf, midi2::available_backends);+  }+  return {};+}++static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>+make_midi1_in(const input_configuration& base_conf, const std::any& api_conf)+{+  if (!api_conf.has_value())+  {+    return make_midi1_in(base_conf);+  }+  else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))+  {+    if (*api_p == libremidi::API::UNSPECIFIED)+    {+      return make_midi1_in(base_conf);+    }+    else+    {+      return make_midi1_in(base_conf, midi_in_configuration_for(*api_p), *api_p);+    }+  }+  else+  {+    if (auto api = libremidi::midi_api(api_conf); api == libremidi::API::UNSPECIFIED)+      return {};+    else+      return make_midi1_in(base_conf, api_conf, libremidi::midi_api(api_conf));+  }+}++/// MIDI 1 constructors+LIBREMIDI_INLINE midi_in::midi_in(const input_configuration& base_conf) noexcept+    : impl_{make_midi1_in(base_conf)}+{+}++LIBREMIDI_INLINE+midi_in::midi_in(input_configuration base_conf, std::any api_conf)+    : impl_{make_midi1_in(base_conf, api_conf)}+{+  if (!impl_)+  {+    error_handler e;+    e.libremidi_handle_error(base_conf, "Could not open midi in for the given api");+    impl_ = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});+  }+}++/// MIDI 2 helpers+static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>+make_midi2_in(const ump_input_configuration& base_conf)+{+  for (const auto& api : available_ump_apis())+  {+    try+    {+      if (auto ret+          = make_midi_in(base_conf, midi_in_configuration_for(api), midi2::available_backends))+        return ret;+    }+    catch (const std::exception& e)+    {+    }+  }++  // No MIDI 2 backend, try the MIDI 1 ones with a wrap:+  {+    auto c2 = convert_midi2_to_midi1_input_configuration(base_conf);+    for (const auto& api : available_apis())+    {+      try+      {+        if (auto ret = make_midi_in(c2, midi_in_configuration_for(api), midi1::available_backends))+          return ret;+      }+      catch (const std::exception& e)+      {+      }+    }+  }++  return {};+}++static LIBREMIDI_INLINE std::unique_ptr<midi_in_api> make_midi2_in(+    const ump_input_configuration& base_conf, const std::any& api_conf, libremidi::API api)+{+  if (is_midi2(api))+  {+    return make_midi_in(base_conf, api_conf, midi2::available_backends);+  }+  else if (is_midi1(api))+  {+    auto c2 = convert_midi2_to_midi1_input_configuration(base_conf);+    return make_midi_in(c2, api_conf, midi1::available_backends);+  }++  return {};+}++static LIBREMIDI_INLINE std::unique_ptr<midi_in_api>+make_midi2_in(const ump_input_configuration& base_conf, const std::any& api_conf)+{+  if (!api_conf.has_value())+  {+    return make_midi2_in(base_conf);+  }+  else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))+  {+    if (*api_p == libremidi::API::UNSPECIFIED)+    {+      return make_midi2_in(base_conf);+    }+    else+    {+      return make_midi2_in(base_conf, midi_in_configuration_for(*api_p), *api_p);+    }+  }+  else+  {+    if (auto api = libremidi::midi_api(api_conf); api == libremidi::API::UNSPECIFIED)+      return {};+    else+      return make_midi2_in(base_conf, api_conf, libremidi::midi_api(api_conf));+  }+}++/// MIDI 2 constructors+LIBREMIDI_INLINE midi_in::midi_in(ump_input_configuration base_conf) noexcept+    : impl_{make_midi2_in(base_conf)}+{+}++LIBREMIDI_INLINE+midi_in::midi_in(ump_input_configuration base_conf, std::any api_conf)+    : impl_{make_midi2_in(base_conf, api_conf)}+{+  if (!impl_)+  {+    error_handler e;+    e.libremidi_handle_error(base_conf, "Could not open midi in for the given api");+    impl_ = std::make_unique<midi_in_dummy>(input_configuration{}, dummy_configuration{});+  }+}++LIBREMIDI_INLINE midi_in::~midi_in() = default;++LIBREMIDI_INLINE midi_in::midi_in(midi_in&& other) noexcept+    : impl_{std::move(other.impl_)}+{+  other.impl_+      = std::make_unique<libremidi::midi_in_dummy>(input_configuration{}, dummy_configuration{});+}++LIBREMIDI_INLINE+stdx::error midi_in::set_port_name(std::string_view portName)+{+  if(impl_->is_port_open())+    return impl_->set_port_name(portName);++  return std::errc::not_connected;+}++LIBREMIDI_INLINE midi_in& midi_in::operator=(midi_in&& other) noexcept+{+  this->impl_ = std::move(other.impl_);+  other.impl_+      = std::make_unique<libremidi::midi_in_dummy>(input_configuration{}, dummy_configuration{});+  return *this;+}++LIBREMIDI_INLINE+libremidi::API midi_in::get_current_api() const noexcept+{+  return impl_->get_current_api();+}++LIBREMIDI_INLINE+stdx::error midi_in::open_port(const input_port& port, std::string_view portName)+{+  if (auto err = impl_->is_client_open(); err != stdx::error{})+    return std::errc::not_connected;++  if (impl_->is_port_open())+    return std::errc::operation_not_supported;++  auto ret = impl_->open_port(port, portName);+  if (ret == stdx::error{})+  {+    impl_->connected_ = true;+    impl_->port_open_ = true;+  }+  return ret;+}++LIBREMIDI_INLINE+stdx::error midi_in::open_virtual_port(std::string_view portName)+{+  if (auto err = impl_->is_client_open(); err != stdx::error{})+    return std::errc::not_connected;++  if (impl_->is_port_open())+    return std::errc::operation_not_supported;++  auto ret = impl_->open_virtual_port(portName);+  if (ret == stdx::error{})+    impl_->port_open_ = true;+  return ret;+}++LIBREMIDI_INLINE+stdx::error midi_in::close_port()+{+  if (auto err = impl_->is_client_open(); err != stdx::error{})+    return std::errc::not_connected;++  auto ret = impl_->close_port();++  impl_->connected_ = false;+  impl_->port_open_ = false;++  return ret;+}++LIBREMIDI_INLINE+bool midi_in::is_port_open() const noexcept+{+  return impl_->is_port_open();+}++LIBREMIDI_INLINE+bool midi_in::is_port_connected() const noexcept+{+  return impl_->is_port_connected();+}++LIBREMIDI_INLINE+int64_t midi_in::absolute_timestamp() const noexcept+{+  return impl_->absolute_timestamp();+}+}
+ vendor/libremidi/midi_out.cpp view
@@ -0,0 +1,326 @@+#if !defined(LIBREMIDI_HEADER_ONLY)+  #include <libremidi/libremidi.hpp>+#endif++#include <libremidi/backends.hpp>+#include <libremidi/detail/midi_api.hpp>++#include <array>+#include <cassert>++namespace libremidi+{+static LIBREMIDI_INLINE std::unique_ptr<midi_out_api>+make_midi_out_impl(auto base_conf, std::any api_conf)+{+  std::unique_ptr<midi_out_api> ptr;+  auto from_api = [&]<typename T>(T& /*backend*/) mutable {+    if (auto conf = std::any_cast<typename T::midi_out_configuration>(&api_conf))+    {+      ptr = libremidi::make<typename T::midi_out>(std::move(base_conf), std::move(*conf));+      return true;+    }+    return false;+  };+  std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi1::available_backends);+  std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends);+  return ptr;+}++static LIBREMIDI_INLINE std::unique_ptr<midi_out_api>+make_midi_out(const output_configuration& base_conf)+{+  for (const auto& api : available_apis())+  {+    try+    {+      if (auto ret = make_midi_out_impl(base_conf, midi_out_configuration_for(api)))+        return ret;+    }+    catch (const std::exception& e)+    {+    }+  }++  for (const auto& api : available_ump_apis())+  {+    try+    {+      if (auto ret = make_midi_out_impl(base_conf, midi_out_configuration_for(api)))+        return ret;+    }+    catch (const std::exception& e)+    {+    }+  }++  return std::make_unique<midi_out_dummy>(output_configuration{}, dummy_configuration{});+}++static LIBREMIDI_INLINE std::unique_ptr<midi_out_api>+make_midi_out(const output_configuration& base_conf, const std::any& api_conf)+{+  if (!api_conf.has_value())+  {+    return make_midi_out(base_conf);+  }+  else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))+  {+    if (*api_p == libremidi::API::UNSPECIFIED)+    {+      return make_midi_out(base_conf);+    }+    else+    {+      return make_midi_out_impl(base_conf, midi_out_configuration_for(*api_p));+    }+  }+  else+  {+    if (auto api = libremidi::midi_api(api_conf); api == libremidi::API::UNSPECIFIED)+      return make_midi_out(base_conf);+    else+      return make_midi_out_impl(base_conf, api_conf);+  }+}++LIBREMIDI_INLINE midi_out::midi_out(const output_configuration& base_conf) noexcept+    : impl_{make_midi_out(base_conf)}+{+}++LIBREMIDI_INLINE+midi_out::midi_out(output_configuration base_conf, std::any api_conf)+    : impl_{make_midi_out(base_conf, api_conf)}+{+  if (!impl_)+  {+    error_handler e;+    e.libremidi_handle_error(base_conf, "Could not open midi out for the given api");+    impl_ = std::make_unique<midi_out_dummy>(output_configuration{}, dummy_configuration{});+  }+}++LIBREMIDI_INLINE midi_out::~midi_out() = default;++LIBREMIDI_INLINE midi_out::midi_out(midi_out&& other) noexcept+    : impl_{std::move(other.impl_)}+{+  other.impl_+      = std::make_unique<libremidi::midi_out_dummy>(output_configuration{}, dummy_configuration{});+}++LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept+{+  this->impl_ = std::move(other.impl_);+  other.impl_+      = std::make_unique<libremidi::midi_out_dummy>(output_configuration{}, dummy_configuration{});+  return *this;+}++LIBREMIDI_INLINE+stdx::error midi_out::set_port_name(std::string_view portName) const+{+  if(impl_->is_port_open())+    return impl_->set_port_name(portName);++  return std::errc::not_connected;+}++LIBREMIDI_INLINE+libremidi::API midi_out::get_current_api() const noexcept+{+  return impl_->get_current_api();+}++LIBREMIDI_INLINE+stdx::error midi_out::open_port(const output_port& port, std::string_view portName) const+{+  if (auto err = impl_->is_client_open(); err != stdx::error{})+    return std::errc::not_connected;++  if (impl_->is_port_open())+    return std::errc::operation_not_supported;++  auto ret = impl_->open_port(port, portName);+  if (ret == stdx::error{})+  {+    impl_->connected_ = true;+    impl_->port_open_ = true;+  }+  return ret;+}++LIBREMIDI_INLINE+stdx::error midi_out::open_virtual_port(std::string_view portName) const+{+  if (auto err = impl_->is_client_open(); err != stdx::error{})+    return std::errc::not_connected;++  if (impl_->is_port_open())+    return std::errc::operation_not_supported;++  auto ret = impl_->open_virtual_port(portName);+  if (ret == stdx::error{})+    impl_->port_open_ = true;+  return ret;+}++LIBREMIDI_INLINE+stdx::error midi_out::close_port() const+{+  if (auto err = impl_->is_client_open(); err != stdx::error{})+    return std::errc::not_connected;++  auto ret = impl_->close_port();+  impl_->connected_ = false;+  impl_->port_open_ = false;+  return ret;+}++LIBREMIDI_INLINE+bool midi_out::is_port_open() const noexcept+{+  return impl_->is_port_open();+}++LIBREMIDI_INLINE+bool midi_out::is_port_connected() const noexcept+{+  return impl_->is_port_connected();+}++LIBREMIDI_INLINE+stdx::error midi_out::send_message(const libremidi::message& message) const+{+  return send_message(message.bytes.data(), message.bytes.size());+}++LIBREMIDI_INLINE+stdx::error midi_out::send_message(std::span<const unsigned char> message) const+{+  return send_message(message.data(), message.size());+}++LIBREMIDI_INLINE+stdx::error midi_out::send_message(unsigned char b0) const+{+  return send_message(&b0, 1);+}++LIBREMIDI_INLINE+stdx::error midi_out::send_message(unsigned char b0, unsigned char b1) const+{+  return send_message(std::to_array({b0, b1}));+}++LIBREMIDI_INLINE+stdx::error midi_out::send_message(unsigned char b0, unsigned char b1, unsigned char b2) const+{+  return send_message(std::to_array({b0, b1, b2}));+}++LIBREMIDI_INLINE+stdx::error midi_out::send_message(const unsigned char* message, size_t size) const+{+#if defined(LIBREMIDI_ASSERTIONS)+  assert(size > 0);+#endif++  return impl_->send_message(message, size);+}++LIBREMIDI_INLINE+int64_t midi_out::current_time()+{+  return impl_->current_time();+}++LIBREMIDI_INLINE+stdx::error midi_out::schedule_message(int64_t ts, const unsigned char* message, size_t size) const+{+#if defined(LIBREMIDI_ASSERTIONS)+  assert(size > 0);+#endif++  return impl_->schedule_message(ts, message, size);+}++LIBREMIDI_INLINE+stdx::error midi_out::send_ump(const uint32_t* message, size_t size) const+{+#if defined(LIBREMIDI_ASSERTIONS)+  assert(size > 0);+  assert(size <= 4);+#endif++  return impl_->send_ump(message, size);+}+LIBREMIDI_INLINE+stdx::error midi_out::send_ump(const libremidi::ump& message) const+{+  return send_ump(message.data, message.size());+}++LIBREMIDI_INLINE+stdx::error midi_out::send_ump(std::span<const uint32_t> message) const+{+  return send_ump(message.data(), message.size());+}++LIBREMIDI_INLINE+stdx::error midi_out::send_ump(uint32_t b0) const+{+  return send_ump(&b0, 1);+}++LIBREMIDI_INLINE+stdx::error midi_out::send_ump(uint32_t b0, uint32_t b1) const+{+  return send_ump(std::to_array({b0, b1}));+}++LIBREMIDI_INLINE+stdx::error midi_out::send_ump(uint32_t b0, uint32_t b1, uint32_t b2) const+{+  return send_ump(std::to_array({b0, b1, b2}));+}++LIBREMIDI_INLINE+stdx::error midi_out::send_ump(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3) const+{+  return send_ump(std::to_array({b0, b1, b2, b3}));+}++LIBREMIDI_INLINE+stdx::error midi_out::send_ump(int32_t b0) const+{+  return send_ump(reinterpret_cast<uint32_t*>(&b0), 1);+}++LIBREMIDI_INLINE+stdx::error midi_out::send_ump(int64_t b01) const+{+  auto ptr = reinterpret_cast<uint32_t*>(&b01);+  uint32_t msg[2]{ptr[1], ptr[0]};+  return send_ump(msg, 2);+}++LIBREMIDI_INLINE+stdx::error midi_out::send_ump(uint64_t b01) const+{+  auto ptr = reinterpret_cast<uint32_t*>(&b01);+  uint32_t msg[2]{ptr[1], ptr[0]};+  return send_ump(msg, 2);+}++LIBREMIDI_INLINE+stdx::error midi_out::schedule_ump(int64_t ts, const uint32_t* message, size_t size) const+{+#if defined(LIBREMIDI_ASSERTIONS)+  assert(size > 0);+#endif++  return impl_->schedule_ump(ts, message, size);+}+}
+ vendor/libremidi/observer.cpp view
@@ -0,0 +1,133 @@+#if !defined(LIBREMIDI_HEADER_ONLY)+  #include <libremidi/libremidi.hpp>+#endif++#include <libremidi/backends.hpp>+#include <libremidi/detail/midi_api.hpp>++namespace libremidi+{++static LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(auto base_conf)+{+  for (const auto& api : available_apis())+  {+    try+    {+      if (auto impl_ = make_observer(base_conf, observer_configuration_for(api)))+        return impl_;+    }+    catch (const std::exception& e)+    {+    }+  }++  for (const auto& api : available_ump_apis())+  {+    try+    {+      if (auto impl_ = make_observer(base_conf, observer_configuration_for(api)))+        return impl_;+    }+    catch (const std::exception& e)+    {+    }+  }++  return std::make_unique<observer_dummy>(observer_configuration{}, dummy_configuration{});+}++LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, std::any api_conf)+{+  std::unique_ptr<observer_api> ptr;+  auto from_api = [&]<typename T>(T& /*backend*/) mutable {+    if (auto conf = std::any_cast<typename T::midi_observer_configuration>(&api_conf))+    {+      ptr = libremidi::make<typename T::midi_observer>(std::move(base_conf), std::move(*conf));+      return true;+    }+    return false;+  };+  std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi1::available_backends);+  std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends);+  return ptr;+}++LIBREMIDI_INLINE std::unique_ptr<observer_api> make_observer(auto base_conf, std::any api_conf)+{+  if (!api_conf.has_value())+  {+    return make_observer(base_conf);+  }+  else if (auto api_p = std::any_cast<libremidi::API>(&api_conf))+  {+    if (*api_p == libremidi::API::UNSPECIFIED)+    {+      return make_observer(base_conf);+    }+    else+    {+      return make_observer_impl(base_conf, observer_configuration_for(*api_p));+    }+  }+  else+  {+    if (auto api = libremidi::midi_api(api_conf); api == libremidi::API::UNSPECIFIED)+      return {};+    else+      return make_observer_impl(base_conf, api_conf);+  }+}++LIBREMIDI_INLINE observer::observer(const observer_configuration& base_conf) noexcept+    : impl_{make_observer(base_conf)}+{+}++LIBREMIDI_INLINE observer::observer(observer_configuration base_conf, std::any api_conf)+    : impl_{make_observer(base_conf, api_conf)}+{+  if (!impl_)+  {+    error_handler e;+    e.libremidi_handle_error(base_conf, "Could not open observer for the given api");+    impl_ = std::make_unique<observer_dummy>(observer_configuration{}, dummy_configuration{});+  }+}++LIBREMIDI_INLINE observer::observer(observer&& other) noexcept+    : impl_{std::move(other.impl_)}+{+  other.impl_ = std::make_unique<libremidi::observer_dummy>(+      observer_configuration{}, dummy_configuration{});+}++LIBREMIDI_INLINE observer& observer::operator=(observer&& other) noexcept+{+  this->impl_ = std::move(other.impl_);+  other.impl_ = std::make_unique<libremidi::observer_dummy>(+      observer_configuration{}, dummy_configuration{});+  return *this;+}++LIBREMIDI_INLINE+observer::~observer() = default;++LIBREMIDI_INLINE+libremidi::API observer::get_current_api() const noexcept+{+  return impl_->get_current_api();+}++LIBREMIDI_INLINE+std::vector<libremidi::input_port> observer::get_input_ports() const noexcept+{+  return impl_->get_input_ports();+}++LIBREMIDI_INLINE+std::vector<libremidi::output_port> observer::get_output_ports() const noexcept+{+  return impl_->get_output_ports();+}+}
+ vendor/libremidi/reader.cpp view
@@ -0,0 +1,752 @@+/*+Copyright (c) 2015, Dimitri Diakopoulos All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+  list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright notice,+  this list of conditions and the following disclaimer in the documentation+  and/or other materials provided with the distribution.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+*/++#if !defined(LIBREMIDI_HEADER_ONLY)+  #include <libremidi/reader.hpp>+#endif+#include <libremidi/message.hpp>++#include <algorithm>+#include <iostream>++// File Parsing Validation Todo:+// ==============================+// [] Bad file name+// [] Bad header+// [] Unknown header type+// [] Bad header size+// [] Bad type+// [] Bad tmecode+// [] Header too short+// [] Track too short+// [] Event too short+// ==============================++#if defined(__LIBREMIDI_DEBUG__)+std::ostream& operator<<(std::ostream& s, const libremidi::message& m)+{+  s << "[ MIDI: ";+  for (auto b : m)+    s << (unsigned int)b << ' ';+  s << "]\n";+  return s;+}+#endif++namespace libremidi+{+namespace util+{+struct no_validator+{+  static inline bool validate_track([[maybe_unused]] const midi_track& track) { return true; }+};++struct validator+{+  static inline bool validate_track(const midi_track& track)+  {+    if (track.empty())+    {+#if defined(__LIBREMIDI_DEBUG__)+      std::cerr << "empty track" << std::endl;+#endif+      return false;+    }++    // Ensure that there is a unique EOT at the end of the track+    auto it = std::find_if(track.begin(), track.end(), [](const libremidi::track_event& msg) {+      static const auto eot = meta_events::end_of_track();+      return msg.m.bytes == eot.bytes;+    });++    if (it == track.end())+    {+#if defined(__LIBREMIDI_DEBUG__)+      std::cerr << "track has no END OF TRACK" << std::endl;+#endif+      return false;+    }++    if (&it->m != &track.back().m)+    {+#if defined(__LIBREMIDI_DEBUG__)+      std::cerr << std::distance(it, track.end());+      std::cerr << "track does not end with END OF TRACK" << std::endl;+#endif+      return false;+    }++    return true;+  }+};++// Used when we know that we have enough space+struct read_unchecked+{+  // Read a MIDI-style variable-length integer (big-endian value in groups of 7 bits,+  // with top bit set to signify that another byte follows).+  static inline void ensure_size(+      [[maybe_unused]] const uint8_t* begin, [[maybe_unused]] const uint8_t* end,+      [[maybe_unused]] int64_t needed)+  {+  }++  static inline uint32_t+  read_variable_length(uint8_t const*& data, [[maybe_unused]] uint8_t const* end)+  {+    uint32_t result = 0;+    while (true)+    {+      uint8_t b = *data++;+      if (b & 0x80)+      {+        result += (b & 0x7F);+        result <<= 7;+      }+      else+      {+        return result + b; // b is the last byte+      }+    }+  }++  static inline void read_bytes(+      midi_bytes& buffer, uint8_t const*& data, [[maybe_unused]] const uint8_t* end,+      const std::size_t num)+  {+    buffer.reserve(buffer.size() + num);+    for (std::size_t i = 0; i < num; ++i)+      buffer.push_back(*data++);+  }++  static inline void read_bytes(+      midi_bytes& buffer, uint8_t const*& data, [[maybe_unused]] const uint8_t* end, const int num)+  {+    read_bytes(buffer, data, end, static_cast<std::size_t>(num));+  }++  static inline uint16_t read_uint16_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end)+  {+    uint16_t result = *data++ << 8u;+    result += *data++;+    return result;+  }++  static inline uint32_t read_uint24_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end)+  {+    uint32_t result = *data++ << 16u;+    result += static_cast<uint32_t>(*data++ << 8u);+    result += *data++;+    return result;+  }++  static inline uint32_t read_uint32_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end)+  {+    uint32_t result = *data++ << 24u;+    result += static_cast<uint32_t>(*data++ << 16u);+    result += static_cast<uint32_t>(*data++ << 8u);+    result += *data++;+    return result;+  }+};++// Used when we do not know if we have enough bytes and have to check before reading+struct read_checked+{+  // Read a MIDI-style variable-length integer (big-endian value in groups of 7 bits,+  // with top bit set to signify that another byte follows).+  static inline void+  ensure_size(const uint8_t* begin, const uint8_t* end, const std::size_t needed)+  {+    if (const auto available = static_cast<std::size_t>(end - begin); available < needed)+      throw std::runtime_error("MIDI reader: not enough data to process");+  }++  static inline std::size_t read_variable_length(uint8_t const*& data, uint8_t const* end)+  {+    std::size_t result = 0;+    while (true)+    {+      ensure_size(data, end, 1);+      uint8_t b = *data++;+      if (b & 0x80)+      {+        result += (b & 0x7F);+        result <<= 7;+      }+      else+      {+        return result + b; // b is the last byte+      }+    }+  }++  static inline void+  read_bytes(midi_bytes& buffer, uint8_t const*& data, uint8_t const* end, const std::size_t num)+  {+    ensure_size(data, end, num);+    read_unchecked::read_bytes(buffer, data, end, num);+  }++  static inline uint16_t read_uint16_be(uint8_t const*& data, uint8_t const* end)+  {+    ensure_size(data, end, 2);+    return read_unchecked::read_uint16_be(data, end);+  }++  static inline uint32_t read_uint24_be(uint8_t const*& data, uint8_t const* end)+  {+    ensure_size(data, end, 3);+    return read_unchecked::read_uint24_be(data, end);+  }++  static inline uint32_t read_uint32_be(uint8_t const*& data, uint8_t const* end)+  {+    ensure_size(data, end, 4);+    return read_unchecked::read_uint32_be(data, end);+  }+};+}++#if defined(LIBREMIDI_UNCHECKED)+using byte_reader = util::read_unchecked;+#else+using byte_reader = util::read_checked;+#endif++#if defined(LIBREMIDI_UNVALIDATED)+using validator = util::no_validator;+#else+using validator = util::validator;+#endif++LIBREMIDI_INLINE+track_event parse_event(+    int tick, int track, const uint8_t*& dataStart, const uint8_t* dataEnd,+    message_type lastEventTypeByte)+{+  byte_reader::ensure_size(dataStart, dataEnd, 1);+  auto type = static_cast<message_type>(*dataStart++);++  track_event event{tick, track, message{}};++  if ((static_cast<uint8_t>(type) & 0xF0) == 0xF0)+  {+    // Meta event+    if (static_cast<uint8_t>(type) == 0xFF)+    {+      byte_reader::ensure_size(dataStart, dataEnd, 1);+      auto subtype = static_cast<meta_event_type>(*dataStart++);++      event.m.bytes.reserve(3);+      event.m.bytes.push_back(static_cast<uint8_t>(type));+      event.m.bytes.push_back(static_cast<uint8_t>(subtype));++      uint32_t length = 0;+      // Here we read the meta-event length manually, as this way we can also put it into+      // event.m.bytes+      while (true)+      {+        byte_reader::ensure_size(dataStart, dataEnd, 1);+        uint8_t b = *dataStart++;+        event.m.bytes.push_back(b);+        if (b & 0x80)+        {+          const uint8_t byte = (b & 0x7F);++          length += byte;+          length <<= 7;+        }+        else+        {+          length += b; // b is the last byte+          break;+        }+      }++      switch (subtype)+      {+        case meta_event_type::SEQUENCE_NUMBER: {+          switch (length)+          {+            case 0:+              return event;+            case 2:+              byte_reader::read_bytes(event.m.bytes, dataStart, dataEnd, 2);+              return event;+            default:+              throw std::invalid_argument("Expected length for SEQUENCE_NUMBER event is 0 or 2");+          }+        }+        case meta_event_type::TEXT:+        case meta_event_type::COPYRIGHT:+        case meta_event_type::TRACK_NAME:+        case meta_event_type::INSTRUMENT:+        case meta_event_type::LYRIC:+        case meta_event_type::MARKER:+        case meta_event_type::CUE:+        case meta_event_type::PATCH_NAME:+        case meta_event_type::DEVICE_NAME: {+          byte_reader::read_bytes(event.m.bytes, dataStart, dataEnd, length);+          return event;+        }++        case meta_event_type::END_OF_TRACK: {+          if (length != 0)+            throw std::invalid_argument("Expected length for END_OF_TRACK event is 0");+          return event;+        }+        case meta_event_type::TEMPO_CHANGE: {+          if (length != 3)+            throw std::invalid_argument("Expected length for TEMPO_CHANGE event is 3");+          // event.m.bytes[3] = read_uint24_be(dataStart); // @dimitri TOFIX+          byte_reader::read_bytes(event.m.bytes, dataStart, dataEnd, length);+          return event;+        }+        case meta_event_type::SMPTE_OFFSET: {+          if (length != 5)+            throw std::invalid_argument("Expected length for SMPTE_OFFSET event is 5");+          byte_reader::read_bytes(event.m.bytes, dataStart, dataEnd, length);+          auto& b = event.m.bytes;++          uint8_t format = (b[3] & 0b01100000) >> 5;+          uint8_t h = (b[3] & 0b00011111);++          if (format > 3)+            throw std::invalid_argument("SMPTE_OFFSET has unknown format");++          int max = 0;+          switch (format)+          {+            case 0: // 24+              max = 24;+              break;+            case 1: // 25+              max = 25;+              break;+            case 2: // 29+              max = 29;+              break;+            case 3: // 30+              max = 30;+              break;+            default:+              break;+          }++          if (h >= 24 || b[4] >= 60 || b[5] >= 60 || b[6] >= max || b[7] >= 100)+            throw std::invalid_argument("SMPTE_OFFSET is out-of-23:59:59:xx:99 bounds");+          return event;+        }+        case meta_event_type::TIME_SIGNATURE: {+          if (length != 4)+            throw std::invalid_argument("Expected length for TIME_SIGNATURE event is 4");+          byte_reader::read_bytes(event.m.bytes, dataStart, dataEnd, length);+          return event;+        }+        case meta_event_type::KEY_SIGNATURE: {+          if (length != 2)+            throw std::invalid_argument("Expected length for KEY_SIGNATURE event is 2");+          byte_reader::read_bytes(event.m.bytes, dataStart, dataEnd, length);+          auto k = static_cast<int8_t>(event.m[3]);+          if (k < -7 || k > 7)+            throw std::invalid_argument("Invalid KEY_SIGNATURE");+          if (event.m[4] > 1)+            throw std::invalid_argument("Invalid KEY_SIGNATURE");+          return event;+        }+        case meta_event_type::PROPRIETARY: {+          byte_reader::read_bytes(event.m.bytes, dataStart, dataEnd, length);+          return event;+        }+        case meta_event_type::CHANNEL_PREFIX: {+          if (length != 1)+            throw std::invalid_argument("Expected length for CHANNEL_PREFIX event is 1");+          byte_reader::read_bytes(event.m.bytes, dataStart, dataEnd, length);+          return event;+        }+        case meta_event_type::MIDI_PORT: {+          if (length != 1)+            throw std::invalid_argument("Expected length for MIDI_PORT event is 1");+          byte_reader::read_bytes(event.m.bytes, dataStart, dataEnd, length);+          return event;+        }+        case meta_event_type::UNKNOWN:+        default: {+          // Unknown events?+          byte_reader::read_bytes(event.m.bytes, dataStart, dataEnd, length);+          return event;+        }+      }+    }++    else if (type == message_type::SYSTEM_EXCLUSIVE)+    {+      const auto length = byte_reader::read_variable_length(dataStart, dataEnd);+      event.m.bytes = {static_cast<uint8_t>(type)};+      byte_reader::read_bytes(event.m.bytes, dataStart, dataEnd, length);+      return event;+    }++    else if (type == message_type::EOX)+    {+      const auto length = byte_reader::read_variable_length(dataStart, dataEnd);+      byte_reader::read_bytes(event.m.bytes, dataStart, dataEnd, length);+      return event;+    }+    else+    {+      throw std::runtime_error("Unrecognised MIDI event type byte");+    }+  }++  // Channel events+  else+  {+    event.m.bytes.clear();++    // Running status...+    if ((static_cast<uint8_t>(type) & 0x80) == 0)+    {+      // Reuse lastEventTypeByte as the event type.+      // eventTypeByte is actually the first parameter+      event.m.bytes.push_back(static_cast<uint8_t>(lastEventTypeByte));+      event.m.bytes.push_back(static_cast<uint8_t>(type));+      type = lastEventTypeByte;+    }+    else+    {+      event.m.bytes.push_back(static_cast<uint8_t>(type));++      byte_reader::ensure_size(dataStart, dataEnd, 1);+      event.m.bytes.push_back(static_cast<uint8_t>(*dataStart++));+      lastEventTypeByte = type;+    }++    static constexpr auto validate = [](midi_bytes& b) {+      if (b[1] < 128 && b[2] < 128)+        return true;+      throw std::invalid_argument("MIDI message has arguments > 127");+    };++    switch (static_cast<message_type>(static_cast<uint8_t>(type) & 0xF0))+    {+      case message_type::NOTE_OFF:+      case message_type::NOTE_ON:+      case message_type::POLY_PRESSURE:+      case message_type::CONTROL_CHANGE:+        byte_reader::ensure_size(dataStart, dataEnd, 1);+        event.m.bytes.push_back(*dataStart++);+        validate(event.m.bytes);+        return event;+      case message_type::PROGRAM_CHANGE:+        if (event.m.bytes[1] >= 128)+          throw std::invalid_argument("MIDI PC has arguments > 127");+        return event;+      case message_type::AFTERTOUCH:+        if (event.m.bytes[1] >= 128)+          throw std::invalid_argument("MIDI Atertouch has arguments > 127");+        return event;+      case message_type::PITCH_BEND:+        byte_reader::ensure_size(dataStart, dataEnd, 1);+        event.m.bytes.push_back(*dataStart++);+        validate(event.m.bytes);+        return event;++      case message_type::TIME_CODE:+        throw std::runtime_error("Unsupported MIDI event type TIME_CODE");+      case message_type::SONG_POS_POINTER:+        throw std::runtime_error("Unsupported MIDI event type SONG_POS_POINTER");+      case message_type::SONG_SELECT:+        throw std::runtime_error("Unsupported MIDI event type SONG_SELECT");+      case message_type::RESERVED1:+        throw std::runtime_error("Unsupported MIDI event type RESERVED1");+      case message_type::RESERVED2:+        throw std::runtime_error("Unsupported MIDI event type RESERVED2");+      case message_type::TUNE_REQUEST:+        throw std::runtime_error("Unsupported MIDI event type TUNE_REQUEST");+      case message_type::EOX:+        throw std::runtime_error("Unsupported MIDI event type EOX");+        // System Realtime Messages :+      case message_type::TIME_CLOCK:+        throw std::runtime_error("Unsupported MIDI event type TIME_CLOCK");+      case message_type::RESERVED3:+        throw std::runtime_error("Unsupported MIDI event type RESERVED3");+      case message_type::START:+        throw std::runtime_error("Unsupported MIDI event type START");+      case message_type::CONTINUE:+        throw std::runtime_error("Unsupported MIDI event type CONTINUE");+      case message_type::STOP:+        throw std::runtime_error("Unsupported MIDI event type STOP");+      case message_type::RESERVED4:+        throw std::runtime_error("Unsupported MIDI event type RESERVED4");+      case message_type::ACTIVE_SENSING:+        throw std::runtime_error("Unsupported MIDI event type ACTIVE_SENSING");+      case message_type::SYSTEM_RESET:+        throw std::runtime_error("Unsupported MIDI event type SYSTEM_RESET");+      case message_type::INVALID:+        throw std::runtime_error("Unsupported MIDI event type INVALID");+      case message_type::SYSTEM_EXCLUSIVE:+        throw std::runtime_error("Unsupported MIDI event type SYSTEM_EXCLUSIVE");+      default:+        throw std::runtime_error("Unsupported MIDI event type");+    }+  }+}++LIBREMIDI_INLINE+reader::reader(bool useAbsolute)+    : ticksPerBeat(480)+    , startingTempo(120)+    , useAbsoluteTicks(useAbsolute)+{+}++LIBREMIDI_INLINE+reader::~reader() { }++constexpr int str_to_headerid(const char* str)+{+  return str[0] << 24 | str[1] << 16 | str[2] << 8 | str[3];+}++LIBREMIDI_INLINE+auto reader::parse(const uint8_t* dataPtr, std::size_t size) noexcept -> parse_result+try+{+  using namespace libremidi::util;++  tracks.clear();++  if (size == 0)+  {+#if defined(__LIBREMIDI_DEBUG__)+    std::cerr << "empty buffer passed to parse." << std::endl;+#endif+    return parse_result::invalid;+  }++  const uint8_t* const dataEnd = dataPtr + size;++  uint32_t headerId = read_checked::read_uint32_be(dataPtr, dataEnd);+  uint32_t headerLength = read_checked::read_uint32_be(dataPtr, dataEnd);++  if (static_cast<int>(headerId) != str_to_headerid("MThd") || headerLength != 6)+  {+#if defined(__LIBREMIDI_DEBUG__)+    std::cerr << "couldn't parse header" << std::endl;+#endif+    return parse_result::invalid;+  }++  format = read_checked::read_uint16_be(+      dataPtr, dataEnd); //@tofix format type -> save for later eventually+  if (format > 2)+  {+#if defined(__LIBREMIDI_DEBUG__)+    std::cerr << "unknown format" << std::endl;+#endif+    return parse_result::invalid;+  }+  int trackCount = read_checked::read_uint16_be(dataPtr, dataEnd);+  uint16_t timeDivision = read_checked::read_uint16_be(dataPtr, dataEnd);++  // CBB: deal with the SMPTE style time coding+  // timeDivision is described here http://www.sonicspot.com/guide/midifiles.html+  if (timeDivision & 0x8000)+  {+#if defined(__LIBREMIDI_DEBUG__)+    std::cerr << "found SMPTE time frames (unsupported)" << std::endl;+    int fps = (timeDivision >> 16) & 0x7f;+    if (fps != -30 && fps != -29 && fps != -25 && fps != -24)+      return parse_result::invalid;+    int ticksPerFrame = timeDivision & 0xff;+#endif+    // given beats per second, timeDivision should be derivable.+    return parse_result::invalid;+  }++  startingTempo = 120.0f;             // midi default+  ticksPerBeat = float(timeDivision); // ticks per beat (a beat is defined as a quarter note)++  parse_result result = parse_result::validated;++  for (int i = 0; i < trackCount; ++i)+  {+    midi_track track;++    headerId = read_checked::read_uint32_be(dataPtr, dataEnd);+    headerLength = read_checked::read_uint32_be(dataPtr, dataEnd);++    if (headerId != str_to_headerid("MTrk"))+    {+#if defined(__LIBREMIDI_DEBUG__)+      std::cerr << "couldn't find track header" << std::endl;+#endif+      return parse_result::incomplete;+    }++    int64_t available = dataEnd - dataPtr;+    if (available < headerLength)+    {+#if defined(__LIBREMIDI_DEBUG__)+      std::cerr << "not enough data available" << std::endl;+#endif+      return parse_result::incomplete;+    }++    track.reserve(headerLength / 3);++    const uint8_t* const trackEnd = dataPtr + headerLength;++    auto runningEvent = message_type::INVALID;++    std::size_t tickCount = 0;++    while (dataPtr < trackEnd)+    {+      const auto tick = read_checked::read_variable_length(dataPtr, trackEnd);+      if (useAbsoluteTicks)+      {+        tickCount += tick;+      }+      else+      {+        tickCount = tick;+      }++      try+      {+        track_event ev+            = parse_event(static_cast<int>(tickCount), i, dataPtr, trackEnd, runningEvent);+        if (!ev.m.empty())+        {+          if (!ev.m.is_meta_event())+          {+            runningEvent = static_cast<message_type>(ev.m.bytes[0]);+          }+        }+        else+        {+#if defined(__LIBREMIDI_DEBUG__)+          std::cerr << "could not read event" << std::endl;+#endif+          dataPtr = trackEnd;+          result = parse_result::incomplete;+          continue;+        }++        track.push_back(std::move(ev));+      }+      catch (const std::exception& e)+      {+#if defined(__LIBREMIDI_DEBUG__)+        std::cerr << "" << e.what() << std::endl;+#endif+        dataPtr = trackEnd;+        result = parse_result::incomplete;+        continue;+      }+    }++    if (result == parse_result::validated)+    {+      if (!validator::validate_track(track))+      {+        result = parse_result::complete;+      }+    }+    tracks.push_back(std::move(track));+  }++  if (result == parse_result::validated)+  {+    if (dataPtr != dataEnd)+    {+#if defined(__LIBREMIDI_DEBUG__)+      std::cerr << "midifile has junk at end: " << std::intptr_t(dataEnd - dataPtr) << std::endl;+#endif+      result = parse_result::complete;+    }+  }+  return result;+}+catch (const std::exception& e)+{+#if defined(__LIBREMIDI_DEBUG__)+  std::cerr << "" << e.what() << std::endl;+#endif+  return parse_result::invalid;+}++// In ticks+LIBREMIDI_INLINE+double reader::get_end_time() const noexcept+{+  if (useAbsoluteTicks)+  {+    double totalLength = 0.;+    for (const auto& t : tracks)+    {+      if (!t.empty())+      {+        const auto& last_event = t.back();+        if (last_event.tick > totalLength)+          totalLength = last_event.tick;+      }+    }+    return totalLength;+  }+  else+  {+    double totalLength = 0.;+    for (const auto& t : tracks)+    {+      double trackLength = 0.;+      for (const auto& e : t)+        trackLength += e.tick;++      if (trackLength > totalLength)+        totalLength = trackLength;+    }+    return totalLength;+  }+}++LIBREMIDI_INLINE+auto reader::parse(const std::vector<uint8_t>& buffer) noexcept -> parse_result+{+  return parse(buffer.data(), buffer.size());+}++#if defined(LIBREMIDI_HAS_SPAN)+LIBREMIDI_INLINE+auto reader::parse(std::span<uint8_t> buffer) noexcept -> parse_result+{+  return parse(buffer.data(), buffer.size());+}+#endif+}
+ vendor/libremidi/writer.cpp view
@@ -0,0 +1,213 @@+/*+Copyright (c) 2015, Dimitri Diakopoulos All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+  list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright notice,+  this list of conditions and the following disclaimer in the documentation+  and/or other materials provided with the distribution.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+*/++#if !defined(LIBREMIDI_HEADER_ONLY)+  #include <libremidi/writer.hpp>+#endif+#include <algorithm>+#include <bit>+#include <ostream>+#include <string>++#if !defined(__cpp_lib_bit_cast)+namespace std+{+template <typename R, typename T>+[[nodiscard]]+constexpr R bit_cast(const T& v) noexcept+{+  union+  {+    R res;+    T init;+  } u{.init = v};+  return u.res;+}+}+#endif+namespace libremidi+{+namespace util+{+template <typename T>+static LIBREMIDI_INLINE std::ostream& write_be(std::ostream& out, T value)+{+  static_assert(+      std::endian::native == std::endian::big || std::endian::native == std::endian::little);+  if constexpr (std::endian::native == std::endian::big)+  {+    out << value;+  }+  else+  {+    static constexpr auto N = sizeof(value);+    struct storage+    {+      uint8_t bytes[N];+    };+    auto data = std::bit_cast<storage>(value);+    std::reverse(data.bytes, data.bytes + N);+    out.write(reinterpret_cast<const char*>(data.bytes), static_cast<std::streamsize>(N));+  }+  return out;+}++// Write a number to the midifile+// as a variable length value which segments a file into 7-bit+// values.  Maximum size of aValue is 0x7fffffff+static LIBREMIDI_INLINE void write_variable_length(uint32_t aValue, std::vector<uint8_t>& outdata)+{+  uint8_t bytes[5] = {0};++  bytes[0] = static_cast<uint8_t>((aValue >> 28) & 0x7F); // most significant 5 bits+  bytes[1] = static_cast<uint8_t>((aValue >> 21) & 0x7F); // next largest 7 bits+  bytes[2] = static_cast<uint8_t>((aValue >> 14) & 0x7F);+  bytes[3] = static_cast<uint8_t>((aValue >> 7) & 0x7F);+  bytes[4] = static_cast<uint8_t>((aValue) & 0x7F); // least significant 7 bits++  int start = 0;+  while (start < 5 && bytes[start] == 0)+    start++;++  for (int i = start; i < 4; i++)+  {+    bytes[i] = bytes[i] | 0x80;+    outdata.push_back(bytes[i]);+  }+  outdata.push_back(bytes[4]);+}++static LIBREMIDI_INLINE void+add_event_track_count_check(std::vector<midi_track>& tracks, int track)+{+  if (track < 0)+    throw std::out_of_range("Refusing to add an event to track " + std::to_string(track) + ".");+  if (track > 65535)+    throw std::out_of_range(+        "Refusing to add an event to track " + std::to_string(track)+        + " ; change add_event_track_count_check in libremidi writer.cpp to increase the limit.");++  while (tracks.size() < static_cast<std::size_t>(track + 1)) // NOLINT(*-misplaced-widening-cast)+    tracks.emplace_back();+}+}++LIBREMIDI_INLINE+void writer::add_event(const int tick, const int track, const message& m)+{+  util::add_event_track_count_check(tracks, track);++  tracks[static_cast<std::size_t>(track)].push_back({tick, track, m});+}++LIBREMIDI_INLINE+void writer::add_event(int track, const track_event& m)+{+  util::add_event_track_count_check(tracks, track);++  tracks[static_cast<std::size_t>(track)].push_back(m);+}++LIBREMIDI_INLINE+void writer::add_track()+{+  util::add_event_track_count_check(tracks, static_cast<int>(tracks.size() + 1));+}++LIBREMIDI_INLINE+void writer::write(std::ostream& out) const+{+  // MIDI File Header+  out.write("MThd", 4);+  util::write_be<uint32_t>(out, 6);+  util::write_be<uint16_t>(out, (tracks.size() == 1) ? 0 : 1);+  util::write_be<uint16_t>(out, static_cast<uint16_t>(tracks.size()));+  util::write_be<uint16_t>(out, ticksPerQuarterNote);++  std::vector<uint8_t> trackRawData;+  for (const auto& event_list : tracks)+  {+    trackRawData.clear();+    // Rough estimation of the memory to allocate+    trackRawData.reserve(event_list.size() * 3);++    for (const auto& event : event_list)+    {+      const auto& msg = event.m;+      if (msg.empty())+        continue;++      // Suppress end-of-track meta messages (one will be added+      // automatically after all track data has been written).+      if (msg.get_meta_event_type() == meta_event_type::END_OF_TRACK)+        continue;++      util::write_variable_length(static_cast<uint32_t>(event.tick), trackRawData);++      if ((msg.get_message_type() == message_type::SYSTEM_EXCLUSIVE)+          || (event.m.get_message_type() == message_type::EOX))+      {+        // 0xf0 == Complete sysex message (0xf0 is part of the raw MIDI).+        // 0xf7 == Raw byte message (0xf7 not part of the raw MIDI).+        // Print the first byte of the message (0xf0 or 0xf7), then+        // print a VLV length for the rest of the bytes in the message.+        // In other words, when creating a 0xf0 or 0xf7 MIDI message,+        // do not insert the VLV byte length yourself, as this code will+        // do it for you automatically.+        trackRawData.emplace_back(msg.bytes[0]); // 0xf0 or 0xf7;++        util::write_variable_length(static_cast<uint32_t>(msg.size()) - 1, trackRawData);++        trackRawData.insert(+            trackRawData.end(), msg.bytes.data() + 1, msg.bytes.data() + msg.bytes.size());+      }+      else+      {+        // Non-sysex type of message, so just output the bytes of the message:+        trackRawData.insert(+            trackRawData.end(), msg.bytes.data(), msg.bytes.data() + msg.bytes.size());+      }+    }++    auto size = trackRawData.size();+    const auto eot = meta_events::end_of_track();++    if ((size < 3) || !((trackRawData[size - 3] == 0xFF) && (trackRawData[size - 2] == 0x2F)))+    {+      trackRawData.emplace_back(0x0); // tick+      trackRawData.emplace_back(eot[0]);+      trackRawData.emplace_back(eot[1]);+      trackRawData.emplace_back(eot[2]);+    }++    // Write the track ID marker "MTrk":+    out.write("MTrk", 4);+    util::write_be<uint32_t>(out, static_cast<uint32_t>(trackRawData.size()));+    out.write(+        reinterpret_cast<char*>(trackRawData.data()),+        static_cast<std::streamsize>(trackRawData.size()));+  }+}+}