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 +26/−0
- libremidi.cabal +201/−0
- src/Libremidi/Api.hs +509/−0
- src/Libremidi/Common.hs +236/−0
- src/Libremidi/Foreign.hsc +253/−0
- src/Libremidi/Simple.hs +113/−0
- test/Main.hs +21/−0
- vendor/libremidi/client.cpp +47/−0
- vendor/libremidi/libremidi-c.cpp +507/−0
- vendor/libremidi/libremidi.cpp +186/−0
- vendor/libremidi/midi_in.cpp +373/−0
- vendor/libremidi/midi_out.cpp +326/−0
- vendor/libremidi/observer.cpp +133/−0
- vendor/libremidi/reader.cpp +752/−0
- vendor/libremidi/writer.cpp +213/−0
+ 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()));+ }+}+}