packages feed

PortMidi 0.1.6.1 → 0.2.0.1

raw patch · 30 files changed

Files

+ CHANGELOG.md view
@@ -0,0 +1,189 @@+# Changelog++This project adheres to [Haskell PVP](https://pvp.haskell.org/).++The format of this changelog is based on+[Keep a Changelog](http://keepachangelog.com/en/1.0.0/)+++## 0.2.0.1+### Changed+- Link to the shared PortMidi library per default.+  No user visible changes, but difference in how to build the package.++## 0.2.0.0+### Changed+- Error reporting (Breaking change).+  All functions now adhere to the `Either`+  [conventions](http://hackage.haskell.org/package/base-4.11.1.0/docs/Data-Either.html):+    real errors are returned in a `Left`,+    successes are returned in a `Right`.+  The non-errors constructors of `PMError`+    (`NoError` and `GotData`) were moved to a new type `PMSuccess`.++### Added+- The `poll` function which binds to+[`Pm_Poll`](http://portmedia.sourceforge.net/portmidi/doxygen/group__grp__io.html#g344e6ba3edb14d560ccb074d66b56590).+- The `readEventsToBuffer` function, to read events in a user-supplied buffer.+- The `PMSuccess` type representing non-errors, with its associated+    functions `getText` and `getSuccessText`.+++## 0.1.6.1++commit 3b92597d84113969371612fb5c590b9f8c77d53c+Author: Paul H. Liu <paul@thev.net>+Date:   Wed Jul 6 09:34:40 2016 -0700++    Fix case sensitivity, and bump version to 0.1.6.1 to correct version number++commit 2d5266dd325f2c248a9022679abfcebe9b317a95+Author: Paul H. Liu <paul@thev.net>+Date:   Tue Jul 5 21:37:24 2016 -0700++    Move to github, and bump version to 0.1.5.3++commit d7946afcfd7c3204c1ea999ed7fdb9c4e9ff20a7+Author: Paul Liu <paul@thev.net>+Date:   Sun Apr 3 00:46:02 2016 -0700++    PMEvent uses CLong as message type to better handle SysEx.++    Ignore-this: 367f146403228813c914a23bfc2a1248+    - This is a breaking change. Use encodeMsg or decodeMsg to work with PMMsg.++    darcs-hash:20160403074602-da8e5-05c03013660b8fc91f843fa22143ec2b7ea5d7b5++commit d5217416a5477f10a36727254425a572d18b7e4a+Author: Paul Liu <paul@thev.net>+Date:   Sun Apr 3 00:40:29 2016 -0700++    Use LANGUAGE pragma instead of OPTIONS_GHC++    Ignore-this: c65fb70875497b8e6c46f76ee9105dbe++    darcs-hash:20160403074029-da8e5-378ba491b94131265a94f115bf068971f869ea2b++commit 358452adfcf91c2436752f9b95ebcc5d5124db97+Author: Paul Liu <paul@thev.net>+Date:   Sun Apr 3 00:40:09 2016 -0700++    Detect freebsd OS++    Ignore-this: 9f819a332d364679acc2e005488f9375++    darcs-hash:20160403074009-da8e5-16571867d53492c3fb7dc84df8fb8a500a71312a++commit c64496ded49633100864e8e0e192a9333ef8caa5+Author: Paul Liu <paul@thev.net>+Date:   Mon Sep 21 22:17:09 2015 -0700++    Add CHANGELOG.txt to package, and bump version to 0.1.5.2++    Ignore-this: 465173236978f9557dbbc574b5d34a8f++    darcs-hash:20150922051709-da8e5-824c6e4c060e3c85ee3831cebde4a88fd3f8582b++commit fda70ecb49f85fd4dccc5daaecbdd9f96fe1651f+Author: Paul Liu <paul@thev.net>+Date:   Mon Sep 21 11:29:09 2015 -0700++    Update README and bump version to 0.1.5.1++    Ignore-this: 98500cbf8d27a5068949cb458648500e++    darcs-hash:20150921182909-da8e5-fc100f8c68c20229a726f31ce8fd7051e8c13163++commit 044b0f47f3fc3bc6ade39c2972fb4b40c16c4e05+Author: Paul Liu <paul@thev.net>+Date:   Mon Sep 21 11:17:37 2015 -0700++    Bump version to 0.1.5++    Ignore-this: 4fb2ae12ea9145bac0f0459ff3073169++    darcs-hash:20150921181737-da8e5-75d0be5001d877daf0c359687c66408afec0639b++commit a158c93a9d50c8bc56a3be908effd4020f9e7cc0+Author: Mark Lentczner <mark.lentczner@gmail.com>+Date:   Mon Sep 21 11:16:04 2015 -0700++    On some systems, Int is not the same size as CInt.++    Ignore-this: 7bd6231264f14d66dd4c0156cc3266ba++    - Added hidden module Sound.PortMidi.DeviceInfo, moving DeviceInfo and peekDeviceInfo there+     - this allowed it to be a hsc2hs module, enabling peekDeviceInfo to use C generated structure offsets and sizes+    - FFI declarations for C calls that take and return PmError and PmDeviceID have been changed to take and return CInt rather than Int+    - Haskell functions interfacing with C calls have had fromIntegral calls added as needed+    - toPMError convenience function added, which replaced many calls to toEnum+    - FFI declaration for Pm_CountDevices added, countDevices became a Haskell function+    - PMError's toEnum function produces a more useful error message when the "impossible" happens++    darcs-hash:20150921181604-63162-de113c7c4da4f42d274b8b6c0641563c8670f606++commit cfae744f937cf4dfafd9e03bce9e0088a5b375f5+Author: Mark Lentczner <mark.lentczner@gmail.com>+Date:   Mon Sep 21 11:15:00 2015 -0700++    Add strictness annotation when necessary++    Ignore-this: 9e5792f9ad04c797fcd51b244d7485fa++    darcs-hash:20150921181500-63162-dd522488d7f680cf536655d8cb1a878e489dcc98++commit 4fb6f505b2f495bad6ddd1072ff41b51001d9b44+Author: Paul Liu <paul@thev.net>+Date:   Wed May 6 00:33:40 2015 -0700++    Update cabal file, and bump version to 0.1.4++    Ignore-this: 8b1d17f401ebd925a4bc6ff7778b453d++    darcs-hash:20150506073340-da8e5-0cf190250915842e35fc5f3177e576d84fb82026++commit a164e464065c84f3f4e958c53271f930c097f2d3+Author: Paul Liu <paul@thev.net>+Date:   Wed May 6 00:33:10 2015 -0700++    Use withCAString for writeSysEx++    Ignore-this: 9499c201b5fb289cda27c090fdbf1541++    darcs-hash:20150506073310-da8e5-ecd2cf32c5f121cc95b97c1e9a495d189b0391cc++commit c3ad563bbce9fbc25c8470865ded7b070d0d030e+Author: Paul Liu <paul@thev.net>+Date:   Wed Sep 16 11:06:11 2009 -0700++    remove conflicts in cabal file++    Ignore-this: 3ef1f4b655182e4cc5eb861e8f2427f1++    darcs-hash:20090916180611-da8e5-47cff7e32f38c7d54155784dbf2ddee6f1053faf++commit 6b0963d50576c0572155e421a4e6d02c8c649d8c+Author: Paul Liu <paul@thev.net>+Date:   Wed Sep 16 11:01:59 2009 -0700++    fix compilation problem on OS X++    Ignore-this: a87d64d1c1a59bd161edecbde23932e3++    darcs-hash:20090916180159-da8e5-62407ea637a40e789f7e316a6fe181f56bc8fa1f++commit 13c1d569f8566968bd0a214903d11c08a1101a2a+Author: Paul Liu <paul@thev.net>+Date:   Thu Jul 30 14:32:59 2009 -0700++    make cabal work for ghc 6.10.* on OS X++    darcs-hash:20090730213259-da8e5-f5a2558120fba824bb05440a8d7e3a2985fab2ed++commit 8b7e2ea3c5ef6cb2b436e136964c2ee5814ffc02+Author: Paul Liu <paul@thev.net>+Date:   Thu Sep 4 18:59:02 2008 -0700++    PortMidi-0.1 initial release++    darcs-hash:20080905015902-da8e5-902a16abf5d28adda47ab45f6c0927a1042caf09
− CHANGELOG.txt
@@ -1,156 +0,0 @@-commit 3b92597d84113969371612fb5c590b9f8c77d53c-Author: Paul H. Liu <paul@thev.net>-Date:   Wed Jul 6 09:34:40 2016 -0700--    Fix case sensitivity, and bump version to 0.1.6.1 to correct version number--commit 2d5266dd325f2c248a9022679abfcebe9b317a95-Author: Paul H. Liu <paul@thev.net>-Date:   Tue Jul 5 21:37:24 2016 -0700--    Move to github, and bump version to 0.1.5.3--commit d7946afcfd7c3204c1ea999ed7fdb9c4e9ff20a7-Author: Paul Liu <paul@thev.net>-Date:   Sun Apr 3 00:46:02 2016 -0700--    PMEvent uses CLong as message type to better handle SysEx.-    -    Ignore-this: 367f146403228813c914a23bfc2a1248-    - This is a breaking change. Use encodeMsg or decodeMsg to work with PMMsg.-    -    darcs-hash:20160403074602-da8e5-05c03013660b8fc91f843fa22143ec2b7ea5d7b5--commit d5217416a5477f10a36727254425a572d18b7e4a-Author: Paul Liu <paul@thev.net>-Date:   Sun Apr 3 00:40:29 2016 -0700--    Use LANGUAGE pragma instead of OPTIONS_GHC-    -    Ignore-this: c65fb70875497b8e6c46f76ee9105dbe-    -    darcs-hash:20160403074029-da8e5-378ba491b94131265a94f115bf068971f869ea2b--commit 358452adfcf91c2436752f9b95ebcc5d5124db97-Author: Paul Liu <paul@thev.net>-Date:   Sun Apr 3 00:40:09 2016 -0700--    Detect freebsd OS-    -    Ignore-this: 9f819a332d364679acc2e005488f9375-    -    darcs-hash:20160403074009-da8e5-16571867d53492c3fb7dc84df8fb8a500a71312a--commit c64496ded49633100864e8e0e192a9333ef8caa5-Author: Paul Liu <paul@thev.net>-Date:   Mon Sep 21 22:17:09 2015 -0700--    Add CHANGELOG.txt to package, and bump version to 0.1.5.2-    -    Ignore-this: 465173236978f9557dbbc574b5d34a8f-    -    darcs-hash:20150922051709-da8e5-824c6e4c060e3c85ee3831cebde4a88fd3f8582b--commit fda70ecb49f85fd4dccc5daaecbdd9f96fe1651f-Author: Paul Liu <paul@thev.net>-Date:   Mon Sep 21 11:29:09 2015 -0700--    Update README and bump version to 0.1.5.1-    -    Ignore-this: 98500cbf8d27a5068949cb458648500e-    -    darcs-hash:20150921182909-da8e5-fc100f8c68c20229a726f31ce8fd7051e8c13163--commit 044b0f47f3fc3bc6ade39c2972fb4b40c16c4e05-Author: Paul Liu <paul@thev.net>-Date:   Mon Sep 21 11:17:37 2015 -0700--    Bump version to 0.1.5-    -    Ignore-this: 4fb2ae12ea9145bac0f0459ff3073169-    -    darcs-hash:20150921181737-da8e5-75d0be5001d877daf0c359687c66408afec0639b--commit a158c93a9d50c8bc56a3be908effd4020f9e7cc0-Author: Mark Lentczner <mark.lentczner@gmail.com>-Date:   Mon Sep 21 11:16:04 2015 -0700--    On some systems, Int is not the same size as CInt.-    -    Ignore-this: 7bd6231264f14d66dd4c0156cc3266ba-    -    - Added hidden module Sound.PortMidi.DeviceInfo, moving DeviceInfo and peekDeviceInfo there-     - this allowed it to be a hsc2hs module, enabling peekDeviceInfo to use C generated structure offsets and sizes-    - FFI declarations for C calls that take and return PmError and PmDeviceID have been changed to take and return CInt rather than Int-    - Haskell functions interfacing with C calls have had fromIntegral calls added as needed-    - toPMError convenience function added, which replaced many calls to toEnum-    - FFI declaration for Pm_CountDevices added, countDevices became a Haskell function-    - PMError's toEnum function produces a more useful error message when the "impossible" happens-    -    darcs-hash:20150921181604-63162-de113c7c4da4f42d274b8b6c0641563c8670f606--commit cfae744f937cf4dfafd9e03bce9e0088a5b375f5-Author: Mark Lentczner <mark.lentczner@gmail.com>-Date:   Mon Sep 21 11:15:00 2015 -0700--    Add strictness annotation when necessary-    -    Ignore-this: 9e5792f9ad04c797fcd51b244d7485fa-    -    darcs-hash:20150921181500-63162-dd522488d7f680cf536655d8cb1a878e489dcc98--commit 4fb6f505b2f495bad6ddd1072ff41b51001d9b44-Author: Paul Liu <paul@thev.net>-Date:   Wed May 6 00:33:40 2015 -0700--    Update cabal file, and bump version to 0.1.4-    -    Ignore-this: 8b1d17f401ebd925a4bc6ff7778b453d-    -    darcs-hash:20150506073340-da8e5-0cf190250915842e35fc5f3177e576d84fb82026--commit a164e464065c84f3f4e958c53271f930c097f2d3-Author: Paul Liu <paul@thev.net>-Date:   Wed May 6 00:33:10 2015 -0700--    Use withCAString for writeSysEx-    -    Ignore-this: 9499c201b5fb289cda27c090fdbf1541-    -    darcs-hash:20150506073310-da8e5-ecd2cf32c5f121cc95b97c1e9a495d189b0391cc--commit c3ad563bbce9fbc25c8470865ded7b070d0d030e-Author: Paul Liu <paul@thev.net>-Date:   Wed Sep 16 11:06:11 2009 -0700--    remove conflicts in cabal file-    -    Ignore-this: 3ef1f4b655182e4cc5eb861e8f2427f1-    -    darcs-hash:20090916180611-da8e5-47cff7e32f38c7d54155784dbf2ddee6f1053faf--commit 6b0963d50576c0572155e421a4e6d02c8c649d8c-Author: Paul Liu <paul@thev.net>-Date:   Wed Sep 16 11:01:59 2009 -0700--    fix compilation problem on OS X-    -    Ignore-this: a87d64d1c1a59bd161edecbde23932e3-    -    darcs-hash:20090916180159-da8e5-62407ea637a40e789f7e316a6fe181f56bc8fa1f--commit 13c1d569f8566968bd0a214903d11c08a1101a2a-Author: Paul Liu <paul@thev.net>-Date:   Thu Jul 30 14:32:59 2009 -0700--    make cabal work for ghc 6.10.* on OS X-    -    darcs-hash:20090730213259-da8e5-f5a2558120fba824bb05440a8d7e3a2985fab2ed--commit 8b7e2ea3c5ef6cb2b436e136964c2ee5814ffc02-Author: Paul Liu <paul@thev.net>-Date:   Thu Sep 4 18:59:02 2008 -0700--    PortMidi-0.1 initial release-    -    darcs-hash:20080905015902-da8e5-902a16abf5d28adda47ab45f6c0927a1042caf09
LICENSE view
@@ -1,21 +1,12 @@-Copyright (c) 2008 Paul H. Liu <paul@thev.net>--This software is provided 'as-is', without any express or implied-warranty. In no event will the authors be held liable for any damages-arising from the use of this software.+Copyright (c) 2008, Paul H. Liu <paul@thev.net>+All rights reserved. -Permission is granted to anyone to use this software for any purpose,-including commercial applications, and to alter it and redistribute it-freely, subject to the following restrictions:+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -1. The origin of this software must not be misrepresented; you must not-   claim that you wrote the original software. If you use this software-   in a product, an acknowledgment in the product documentation would-   be appreciated but is not required.+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -2. Altered source versions must be plainly marked as such, and must not-   be misrepresented as being the original software.+2. 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. -3. This notice may not be removed or altered from any source-   distribution.+3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +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.
PortMidi.cabal view
@@ -1,67 +1,93 @@+Cabal-Version: 2.2 name:          PortMidi-version:       0.1.6.1-Cabal-Version: >= 1.6+version:       0.2.0.1 build-type:    Simple-license:       BSD3+license:       BSD-3-Clause license-file:  LICENSE maintainer:    Paul H. Liu <paul@thev.net>-homepage:      http://github.com/ninegua/PortMidi+             , Henning Thielemann <haskell@henning-thielemann.de>+homepage:      http://github.com/PortMidi/PortMidi category:      Sound stability:     experimental synopsis:      A binding for PortMedia/PortMidi description:   A Haskell binding for PortMedia/PortMidi+extra-doc-files:+        README.md+        CHANGELOG.md extra-source-files:-        README.txt-        CHANGELOG.txt         portmidi/pm_common/portmidi.h         portmidi/pm_common/pmutil.h         portmidi/pm_common/pminternal.h-        portmidi/pm_linux/pmlinux.h         portmidi/pm_linux/pmlinuxalsa.h-        portmidi/pm_mac/pmmac.h+        portmidi/pm_linux/pmlinuxnull.h         portmidi/pm_mac/pmmacosxcm.h         portmidi/pm_win/pmwinmm.h         portmidi/porttime/porttime.h++Flag vendoredC+  Description: Use bundled C code instead of shared PortMidi library+  Manual: True+  Default: False++Flag pkgConfig+  Description: Use PkgConfig (portmidi.pc) instead of Extra-Libraries+  Manual: True+  Default: False+ Library   exposed-modules: Sound.PortMidi   other-modules:   Sound.PortMidi.DeviceInfo-  build-depends:   base >= 3 && < 5-  extensions:      CPP, ForeignFunctionInterface-  if os(linux) || os(freebsd)-    include-dirs: portmidi/pm_common portmidi/pm_linux portmidi/porttime-    cc-options: -DPMALSA -    c-sources:    +  build-depends:   base >= 4.8 && < 5+  default-language:    Haskell98+  default-extensions:  CPP, ForeignFunctionInterface+  if flag(vendoredC)+    if os(linux) || os(freebsd)+      include-dirs: portmidi/pm_common portmidi/pm_linux portmidi/porttime+      cc-options: -DPMALSA+      c-sources:           portmidi/pm_common/pmutil.c           portmidi/pm_common/portmidi.c           portmidi/pm_linux/pmlinux.c           portmidi/pm_linux/pmlinuxalsa.c+          portmidi/pm_linux/pmlinuxnull.c           portmidi/porttime/porttime.c           portmidi/porttime/ptlinux.c-   extra-libraries: asound-  else-    if os(darwin)-     include-dirs: portmidi/pm_common portmidi/pm_mac portmidi/porttime-     cc-options:   -msse2-     c-sources:  +      extra-libraries: asound+    else+      if os(darwin)+        include-dirs: portmidi/pm_common portmidi/pm_mac portmidi/porttime+        cc-options:   -msse2+        c-sources:           portmidi/pm_common/pmutil.c           portmidi/pm_common/portmidi.c           portmidi/pm_mac/pmmac.c           portmidi/pm_mac/pmmacosxcm.c           portmidi/porttime/porttime.c+          portmidi/porttime/ptmacosx_cf.c           portmidi/porttime/ptmacosx_mach.c-     frameworks: CoreMIDI CoreFoundation CoreAudio+        frameworks: CoreMIDI CoreFoundation CoreAudio+      else+        if os(mingw32)+          include-dirs: portmidi/pm_common portmidi/pm_win portmidi/porttime+          c-sources:+            portmidi/pm_common/pmutil.c+            portmidi/pm_common/portmidi.c+            portmidi/pm_win/pmwin.c+            portmidi/pm_win/pmwinmm.c+            portmidi/porttime/porttime.c+            portmidi/porttime/ptwinmm.c+          extra-libraries: winmm+  else+    if flag(pkgConfig)+      pkgconfig-depends: portmidi     else-      if os(mingw32)-        include-dirs: portmidi/pm_common portmidi/pm_win portmidi/porttime-        c-sources:  -          portmidi/pm_common/pmutil.c-          portmidi/pm_common/portmidi.c-          portmidi/pm_win/pmwin.c-          portmidi/pm_win/pmwinmm.c-          portmidi/porttime/porttime.c-          portmidi/porttime/ptwinmm.c-        extra-libraries: winmm +      extra-libraries: portmidi+ source-repository head   type:         git-  location:     https://github.com/ninegua/PortMidi+  location:     https://github.com/PortMidi/PortMidi-haskell +source-repository this+  tag:          0.2.0.1+  type:         git+  location:     https://github.com/PortMidi/PortMidi-haskell
+ README.md view
@@ -0,0 +1,39 @@+This is a Haskell module for PortMidi audio library, which supports+real-time MIDI input and output.+++ChangeLog+=========++Please see [CHANGELOG.md](CHANGELOG.md).+++Installation+============++The usual cabal installation steps apply, either:++       runhaskell Setup.hs configure+       runhaskell Setup.hs build+       runhaskell Setup.hs install++or simply:++       cabal install++This will install a PortMidi package that contains a `Sound.PortMidi` module.+++Bug reports / Feature requests+==============================++Bug reports and feature requests can be created at+[GitHub](https://github.com/PortMidi/portmidi-haskell/issues).+++Examples+========++* https://hackage.haskell.org/package/Euterpea+* https://hackage.haskell.org/package/portmidi-utility+* https://hackage.haskell.org/package/PortMidi-simple
− README.txt
@@ -1,34 +0,0 @@-This is a Haskell module for PortMidi audio library, which supports-real-time MIDI input and output. --=========-ChangeLog-=========--Please see CHANGELOG.txt.--============-Installation-============--The usual cabal installation steps apply, either: --       runhaskell Setup.hs configure-       runhaskell Setup.hs build-       runhaskell Setup.hs install--or simply:--       cabal install--This will install a PortMidi package that contains a Sound.PortMidi module. --===================-Contact Information-===================--You may send your bug report and feature request to the package -maintainer: Paul H. Liu <paul@thev.net>.-----Last Update: Tue Jul 5 PDT 2016
Sound/PortMidi.hs view
@@ -2,15 +2,18 @@     Interface to PortMidi -} {-# LANGUAGE EmptyDataDecls #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-} -module Sound.PortMidi ( +module Sound.PortMidi (   -- * Data Types     PMError(..)+  , PMSuccess(..)+  , PMEventCount(..)   , PMStream   , DeviceInfo(..)   , DeviceID   , PMMsg(..)-  , PMEvent(..) +  , PMEvent(..)   -- * Constants   , filterActive   , filterSysex@@ -33,11 +36,13 @@   , filterSongSelect   , filterTune   , filterSystemCommon-  -- * PortMid functions+  -- * PortMidi functions   , initialize   , terminate   , hasHostError   , getErrorText+  , getSuccessText+  , getText   , countDevices   , getDefaultInputDeviceID   , getDefaultOutputDeviceID@@ -49,10 +54,12 @@   , setChannelMask   , abort   , close+  , poll   , readEvents+  , readEventsToBuffer   , writeEvents   , writeShort-  , writeSysEx +  , writeSysEx   -- * Time function   , time   --@@ -63,18 +70,28 @@  import Foreign import Foreign.C-import Foreign.Marshal-import Foreign.Storable-import Data.IORef-import Data.Bits-import System.IO.Unsafe  import Sound.PortMidi.DeviceInfo -data PMError-  = NoError+-- | Represents non-errors of the C enum `PmError`+data PMSuccess+  = NoError'NoData+  -- ^ Returned by 'poll' when there is no data, and returned by other functions+  -- when there is no error.   | GotData-  | HostError+  -- ^ Only returned by 'poll' when data is available.+  deriving (Eq, Show)++instance Enum PMSuccess where+  fromEnum NoError'NoData = 0+  fromEnum GotData = 1+  toEnum 0 = NoError'NoData+  toEnum 1 = GotData+  toEnum x = error $ "PortMidi: PMSuccess toEnum out of bound " ++ show x++-- | Represents real errors of the C enum `PmError`+data PMError+  = HostError   | InvalidDeviceId   | InsufficientMemory   | BufferTooSmall@@ -86,8 +103,6 @@   deriving (Eq, Show)  instance Enum PMError where-  fromEnum NoError = 0-  fromEnum GotData = 1   fromEnum HostError = -10000   fromEnum InvalidDeviceId = -9999   fromEnum InsufficientMemory = -9998@@ -97,8 +112,6 @@   fromEnum BadData = -9994   fromEnum InternalError = -9993   fromEnum BufferMaxSize = -9992-  toEnum 0 = NoError-  toEnum 1 = GotData   toEnum (-10000) = HostError   toEnum (-9999) = InvalidDeviceId   toEnum (-9998) = InsufficientMemory@@ -108,22 +121,39 @@   toEnum (-9994) = BadData   toEnum (-9993) = InternalError   toEnum (-9992) = BufferMaxSize-  toEnum x = error $ "PortMidi: toEnum out of bound " ++ show x+  toEnum x = error $ "PortMidi: PMError toEnum out of bound " ++ show x -toPMError :: CInt -> PMError-toPMError = toEnum . fromIntegral+eitherErrorOrSuccess :: CInt -> Either PMError PMSuccess+eitherErrorOrSuccess n+  | isSuccess = Right $ toEnum $ fromIntegral n+  | otherwise = Left $ toEnum $ fromIntegral n+  where+    isSuccess = n == 0 || n == 1 +-- | Represents a count of 'PMEvent's+newtype PMEventCount = PMEventCount CInt+  deriving(Num, Integral, Real, Enum, Show, Eq, Ord)++-- | Interprets a 'CInt', as returned by 'pm_Read'.+eitherErrorOrCount :: CInt -> Either PMError PMEventCount+eitherErrorOrCount n+  | n >= 0    = Right $ fromIntegral n+  | otherwise = Left $ toEnum $ fromIntegral n+ data PortMidiStream type PMStreamPtr = Ptr PortMidiStream type PMStream = ForeignPtr PortMidiStream type DeviceID = Int +(.<.) :: CLong -> Int -> CLong (.<.) = shiftL++(.>.) :: CLong -> Int -> CLong (.>.) = shiftR  filterActive, filterSysex, filterClock, filterPlay, filterTick, filterFD, filterUndefined, filterReset, filterRealtime, filterNote, filterChannelAftertouch, filterPolyAftertouch, filterAftertouch, filterProgram, filterControl, filterPitchBend, filterMTC, filterSongPosition, filterSongSelect, filterTune, filterSystemCommon :: CLong -filterActive = 1 .<. 0x0e +filterActive = 1 .<. 0x0e filterSysex = 1 .<. 0x00 filterClock = 1 .<. 0x08 filterPlay = (1 .<. 0x0A) .|. (1 .<. 0x0C) .|. (1 .<. 0x0B)@@ -131,7 +161,7 @@ filterFD = 1 .<. 0x0D filterUndefined = filterFD filterReset = 1 .<. 0x0F-filterRealtime = filterActive .|. filterSysex .|. filterClock .|. filterPlay .|. filterUndefined .|. filterReset .|. filterTick +filterRealtime = filterActive .|. filterSysex .|. filterClock .|. filterPlay .|. filterUndefined .|. filterReset .|. filterTick filterNote = (1 .<. 0x19) .|. (1 .<. 0x18) filterChannelAftertouch = 1 .<. 0x1D filterPolyAftertouch = 1 .<. 0x1A@@ -145,11 +175,11 @@ filterTune = 1 .<. 0x06 filterSystemCommon = filterMTC .|. filterSongPosition .|. filterSongSelect .|. filterTune -data PMMsg +data PMMsg   =  PMMsg-  { status :: !CLong-  , data1  :: !CLong-  , data2  :: !CLong+  { status :: {-# UNPACK #-} !CLong+  , data1  :: {-# UNPACK #-} !CLong+  , data2  :: {-# UNPACK #-} !CLong   } deriving (Eq, Show)  encodeMsg :: PMMsg -> CLong@@ -157,12 +187,13 @@ decodeMsg :: CLong -> PMMsg decodeMsg i = PMMsg (i .&. 0xFF) ((i .>. 8) .&. 0xFF) ((i .>. 16) .&. 0xFF) +-- | Time with millisecond precision. type Timestamp = CULong -data PMEvent -  =  PMEvent -  { message   :: !CLong-  , timestamp :: !Timestamp+data PMEvent+  =  PMEvent+  { message   :: {-# UNPACK #-} !CLong+  , timestamp :: {-# UNPACK #-} !Timestamp   } deriving (Eq, Show)  instance Storable PMEvent where@@ -178,12 +209,12 @@   foreign import ccall "portmidi.h Pm_Initialize" pm_Initialize :: IO CInt-initialize :: IO PMError-initialize = pm_Initialize >>= return . toPMError+initialize :: IO (Either PMError PMSuccess)+initialize = pm_Initialize >>= return . eitherErrorOrSuccess  foreign import ccall "portmidi.h Pm_Terminate" pm_Terminate :: IO CInt-terminate :: IO PMError-terminate = pm_Terminate >>= return . toPMError+terminate :: IO (Either PMError PMSuccess)+terminate = pm_Terminate >>= return . eitherErrorOrSuccess  foreign import ccall "portmidi.h Pm_HasHostError" pm_HasHostError :: PMStreamPtr -> IO CInt hasHostError :: PMStream -> IO Bool@@ -193,6 +224,12 @@ getErrorText :: PMError -> IO String getErrorText err = pm_GetErrorText (fromIntegral $ fromEnum err) >>= peekCString +getSuccessText :: PMSuccess -> IO String+getSuccessText success = pm_GetErrorText (fromIntegral $ fromEnum success) >>= peekCString++getText :: Either PMError PMSuccess -> IO String+getText = either getErrorText getSuccessText+ foreign import ccall "portmidi.h Pm_CountDevices" pm_countDevices :: IO CInt countDevices :: IO DeviceID countDevices = pm_countDevices >>= return . fromIntegral@@ -213,72 +250,88 @@ getDeviceInfo deviceID = pm_GetDeviceInfo (fromIntegral deviceID) >>= peekDeviceInfo  foreign import ccall "portmidi.h Pm_OpenInput" pm_OpenInput :: Ptr PMStreamPtr -> CInt -> Ptr () -> CLong -> Ptr () -> Ptr () -> IO CInt-openInput :: DeviceID -> IO (Either PMStream PMError)-openInput inputDevice = -  with nullPtr (\ptr -> do-    e <- pm_OpenInput ptr (fromIntegral inputDevice) nullPtr 0 nullPtr nullPtr-    if e == 0 -      then do+openInput :: DeviceID -> IO (Either PMError PMStream)+openInput inputDevice =+  with nullPtr (\ptr ->+    eitherErrorOrSuccess <$> pm_OpenInput ptr (fromIntegral inputDevice) nullPtr 0 nullPtr nullPtr >>= either+      (return . Left)+      (\_ -> do         stream <- peek ptr-        ptr' <- newForeignPtr_ stream-        return $ Left ptr'-      else return (Right (toPMError e)))-      +        Right <$> newForeignPtr_ stream))+ foreign import ccall "portmidi.h Pm_OpenOutput" pm_OpenOutput :: Ptr PMStreamPtr -> CInt -> Ptr () -> CLong -> Ptr () -> Ptr () -> CLong -> IO CInt-openOutput :: DeviceID -> Int -> IO (Either PMStream PMError)+openOutput :: DeviceID -> Int -> IO (Either PMError PMStream) openOutput outputDevice latency =   with nullPtr (\ptr -> do-    e <- pm_OpenOutput ptr (fromIntegral outputDevice) nullPtr 0 nullPtr nullPtr (fromIntegral latency)-    if e == 0 -      then do+    eitherErrorOrSuccess <$> pm_OpenOutput ptr (fromIntegral outputDevice) nullPtr 0 nullPtr nullPtr (fromIntegral latency) >>= either+      (return . Left)+      (\_ -> do         stream <- peek ptr-        ptr' <- newForeignPtr_ stream-        return $ Left ptr'-      else return (Right (toPMError e)))+        Right <$> newForeignPtr_ stream))  foreign import ccall "portmidi.h Pm_SetFilter" pm_SetFilter :: PMStreamPtr -> CLong -> IO CInt-setFilter :: PMStream -> CLong -> IO PMError-setFilter stream filter = withForeignPtr stream (\s -> pm_SetFilter s filter >>= return . toPMError)+setFilter :: PMStream -> CLong -> IO (Either PMError PMSuccess)+setFilter stream theFilter = withForeignPtr stream (fmap eitherErrorOrSuccess . flip pm_SetFilter theFilter)  channel :: Int -> CLong channel i = 1 .<. i  foreign import ccall "portmidi.h Pm_SetChannelMask" pm_SetChannelMask :: PMStreamPtr -> CLong -> IO CInt-setChannelMask :: PMStream -> CLong -> IO PMError-setChannelMask stream mask = withForeignPtr stream (\s -> pm_SetChannelMask s mask >>= return . toPMError)+setChannelMask :: PMStream -> CLong -> IO (Either PMError PMSuccess)+setChannelMask stream mask = withForeignPtr stream (fmap eitherErrorOrSuccess . flip pm_SetChannelMask mask)  foreign import ccall "portmidi.h Pm_Abort" pm_Abort :: PMStreamPtr -> IO CInt-abort :: PMStream -> IO PMError-abort = flip withForeignPtr (\s -> pm_Abort s >>= return . toPMError)+abort :: PMStream -> IO (Either PMError PMSuccess)+abort = flip withForeignPtr (fmap eitherErrorOrSuccess . pm_Abort)  foreign import ccall "portmidi.h Pm_Close" pm_Close :: PMStreamPtr -> IO CInt-close :: PMStream -> IO PMError-close = flip withForeignPtr (\s -> pm_Close s >>= return . toPMError)+close :: PMStream -> IO (Either PMError PMSuccess)+close = flip withForeignPtr (fmap eitherErrorOrSuccess . pm_Close) +foreign import ccall "portmidi.h Pm_Poll" pm_Poll :: PMStreamPtr -> IO CInt+-- | Returns wether or not a subsequent call to 'readEvents' would return+-- some 'PMEvent's or not.+poll :: PMStream -> IO (Either PMError PMSuccess)+poll = flip withForeignPtr (fmap eitherErrorOrSuccess . pm_Poll)+ foreign import ccall "portmidi.h Pm_Read" pm_Read :: PMStreamPtr -> Ptr PMEvent -> CLong -> IO CInt-readEvents :: PMStream -> IO (Either [PMEvent] PMError)-readEvents = flip withForeignPtr (\s -> allocaArray (fromIntegral defaultBufferSize) (\arr -> do-  r <- pm_Read s arr defaultBufferSize-  if r > 0-    then peekArray (fromIntegral r) arr >>= return . Left-    else return $ Right (toPMError r)))-  where-    defaultBufferSize = 256+-- | Reads at most 256 'PMEvent's, using a dynamically allocated buffer.+readEvents :: PMStream -> IO (Either PMError [PMEvent])+readEvents stream =+  allocaArray (fromIntegral defaultBufferSize) $ \arr ->+    readEventsToBuffer stream arr defaultBufferSize >>= either+      (return . Left)+      (fmap Right . flip peekArray arr . fromIntegral)+ where+  defaultBufferSize = 256 +-- | Reads 'PMEvent's and writes them to the buffer passed as argument.+readEventsToBuffer :: PMStream+                   -> Ptr PMEvent+                   -- ^ The 'PMEvent's buffer which will contain the results.+                   -> CLong+                   -- ^ The size of the 'PMEvent' buffer, in number of elements.+                   -- No more that this number of 'PMEvent's can be read at once.+                   -> IO (Either PMError PMEventCount)+                   -- ^ When 'Right', returns the number of elements written+                   -- to the 'PMEvent' buffer.+readEventsToBuffer stream ptr sz =+  withForeignPtr stream $ \s ->+    eitherErrorOrCount <$> pm_Read s ptr sz+ foreign import ccall "portmidi.h Pm_Write" pm_Write :: PMStreamPtr -> Ptr PMEvent -> CLong -> IO CInt-writeEvents :: PMStream -> [PMEvent] -> IO PMError-writeEvents stream events = withForeignPtr stream (\s -> -  withArrayLen events (\len arr -> pm_Write s arr (fromIntegral len) >>= return . toPMError))+writeEvents :: PMStream -> [PMEvent] -> IO (Either PMError PMSuccess)+writeEvents stream events = withForeignPtr stream (\s ->+  withArrayLen events (\len arr -> eitherErrorOrSuccess <$> pm_Write s arr (fromIntegral len)))  foreign import ccall "portmidi.h Pm_WriteShort" pm_WriteShort :: PMStreamPtr -> CULong -> CLong -> IO CInt-writeShort :: PMStream -> PMEvent -> IO PMError-writeShort stream (PMEvent msg time) = withForeignPtr stream (\s ->-  pm_WriteShort s time msg >>= return . toPMError)+writeShort :: PMStream -> PMEvent -> IO (Either PMError PMSuccess)+writeShort stream (PMEvent msg t) = withForeignPtr stream (\s ->+  eitherErrorOrSuccess <$> pm_WriteShort s t msg)  foreign import ccall "portmidi.h Pm_WriteSysEx" pm_WriteSysEx :: PMStreamPtr -> CULong -> CString -> IO CInt-writeSysEx :: PMStream -> Timestamp -> String -> IO PMError-writeSysEx stream time str = withForeignPtr stream (\st ->-  withCAString str (\s -> pm_WriteSysEx st time s >>= return . toPMError))--foreign import ccall "porttime.h Pt_Time" time :: IO Timestamp      +writeSysEx :: PMStream -> Timestamp -> String -> IO (Either PMError PMSuccess)+writeSysEx stream t str = withForeignPtr stream (\st ->+  withCAString str (\s -> eitherErrorOrSuccess <$> pm_WriteSysEx st t s)) +foreign import ccall "porttime.h Pt_Time" time :: IO Timestamp
Sound/PortMidi/DeviceInfo.hsc view
@@ -19,6 +19,7 @@   , opened    :: Bool   } deriving (Eq, Show) +peekDeviceInfo :: Ptr a -> IO DeviceInfo peekDeviceInfo ptr = do   s <- #{peek PmDeviceInfo, interf} ptr >>= peekCString   u <- #{peek PmDeviceInfo, name} ptr >>= peekCString
portmidi/pm_common/pminternal.h view
@@ -1,174 +1,191 @@-/* pminternal.h -- header for interface implementations */
-
-/* this file is included by files that implement library internals */
-/* Here is a guide to implementers:
-     provide an initialization function similar to pm_winmm_init()
-     add your initialization function to pm_init()
-     Note that your init function should never require not-standard
-         libraries or fail in any way. If the interface is not available,
-         simply do not call pm_add_device. This means that non-standard
-         libraries should try to do dynamic linking at runtime using a DLL
-         and return without error if the DLL cannot be found or if there
-         is any other failure.
-     implement functions as indicated in pm_fns_type to open, read, write,
-         close, etc.
-     call pm_add_device() for each input and output device, passing it a
-         pm_fns_type structure.
-     assumptions about pm_fns_type functions are given below.
- */
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* these are defined in system-specific file */
-void *pm_alloc(size_t s);
-void pm_free(void *ptr);
-
-/* if an error occurs while opening or closing a midi stream, set these: */
-extern int pm_hosterror;
-extern char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN];
- 
-struct pm_internal_struct;
-
-/* these do not use PmInternal because it is not defined yet... */
-typedef PmError (*pm_write_short_fn)(struct pm_internal_struct *midi, 
-                                     PmEvent *buffer);
-typedef PmError (*pm_begin_sysex_fn)(struct pm_internal_struct *midi,
-                                     PmTimestamp timestamp);
-typedef PmError (*pm_end_sysex_fn)(struct pm_internal_struct *midi,
-                                   PmTimestamp timestamp);
-typedef PmError (*pm_write_byte_fn)(struct pm_internal_struct *midi,
-                                    unsigned char byte, PmTimestamp timestamp);
-typedef PmError (*pm_write_realtime_fn)(struct pm_internal_struct *midi,
-                                        PmEvent *buffer);
-typedef PmError (*pm_write_flush_fn)(struct pm_internal_struct *midi,
-                                     PmTimestamp timestamp);
-typedef PmTimestamp (*pm_synchronize_fn)(struct pm_internal_struct *midi);
-/* pm_open_fn should clean up all memory and close the device if any part
-   of the open fails */
-typedef PmError (*pm_open_fn)(struct pm_internal_struct *midi,
-                              void *driverInfo);
-typedef PmError (*pm_abort_fn)(struct pm_internal_struct *midi);
-/* pm_close_fn should clean up all memory and close the device if any
-   part of the close fails. */
-typedef PmError (*pm_close_fn)(struct pm_internal_struct *midi);
-typedef PmError (*pm_poll_fn)(struct pm_internal_struct *midi);
-typedef void (*pm_host_error_fn)(struct pm_internal_struct *midi, char * msg,
-                                 unsigned int len);
-typedef unsigned int (*pm_has_host_error_fn)(struct pm_internal_struct *midi);
-
-typedef struct {
-    pm_write_short_fn write_short; /* output short MIDI msg */
-    pm_begin_sysex_fn begin_sysex; /* prepare to send a sysex message */
-    pm_end_sysex_fn end_sysex; /* marks end of sysex message */
-    pm_write_byte_fn write_byte; /* accumulate one more sysex byte */
-    pm_write_realtime_fn write_realtime; /* send real-time message within sysex */
-    pm_write_flush_fn write_flush; /* send any accumulated but unsent data */
-    pm_synchronize_fn synchronize; /* synchronize portmidi time to stream time */
-    pm_open_fn open;   /* open MIDI device */
-    pm_abort_fn abort; /* abort */
-    pm_close_fn close; /* close device */
-    pm_poll_fn poll;   /* read pending midi events into portmidi buffer */
-    pm_has_host_error_fn has_host_error; /* true when device has had host 
-                                            error message */
-    pm_host_error_fn host_error; /* provide text readable host error message
-                                    for device (clears and resets) */
-} pm_fns_node, *pm_fns_type;
-
-
-/* when open fails, the dictionary gets this set of functions: */
-extern pm_fns_node pm_none_dictionary;
-
-typedef struct {
-    PmDeviceInfo pub; /* some portmidi state also saved in here (for autmatic
-                         device closing (see PmDeviceInfo struct) */
-    void *descriptor; /* ID number passed to win32 multimedia API open */
-    void *internalDescriptor; /* points to PmInternal device, allows automatic 
-                                 device closing */
-    pm_fns_type dictionary;
-} descriptor_node, *descriptor_type;
-
-extern int pm_descriptor_max;
-extern descriptor_type descriptors;
-extern int pm_descriptor_index;
-
-typedef unsigned long (*time_get_proc_type)(void *time_info);
-
-typedef struct pm_internal_struct {
-    int device_id; /* which device is open (index to descriptors) */
-    short write_flag; /* MIDI_IN, or MIDI_OUT */
-    
-    PmTimeProcPtr time_proc; /* where to get the time */
-    void *time_info; /* pass this to get_time() */
-    long buffer_len; /* how big is the buffer or queue? */
-    PmQueue *queue;
-
-    long latency; /* time delay in ms between timestamps and actual output */
-                  /* set to zero to get immediate, simple blocking output */
-                  /* if latency is zero, timestamps will be ignored; */
-                  /* if midi input device, this field ignored */
-    
-    int sysex_in_progress; /* when sysex status is seen, this flag becomes
-        * true until EOX is seen. When true, new data is appended to the
-        * stream of outgoing bytes. When overflow occurs, sysex data is 
-        * dropped (until an EOX or non-real-timei status byte is seen) so
-        * that, if the overflow condition is cleared, we don't start 
-        * sending data from the middle of a sysex message. If a sysex
-        * message is filtered, sysex_in_progress is false, causing the
-        * message to be dropped. */
-    PmMessage sysex_message; /* buffer for 4 bytes of sysex data */
-    int sysex_message_count; /* how many bytes in sysex_message so far */
-
-    long filters; /* flags that filter incoming message classes */
-    int channel_mask; /* flter incoming messages based on channel */
-    PmTimestamp last_msg_time; /* timestamp of last message */
-    PmTimestamp sync_time; /* time of last synchronization */
-    PmTimestamp now; /* set by PmWrite to current time */
-    int first_message; /* initially true, used to run first synchronization */
-    pm_fns_type dictionary; /* implementation functions */
-    void *descriptor; /* system-dependent state */
-    /* the following are used to expedite sysex data */
-    /* on windows, in debug mode, based on some profiling, these optimizations
-     * cut the time to process sysex bytes from about 7.5 to 0.26 usec/byte,
-     * but this does not count time in the driver, so I don't know if it is
-     * important
-     */
-    unsigned char *fill_base; /* addr of ptr to sysex data */
-    unsigned long *fill_offset_ptr; /* offset of next sysex byte */
-    int fill_length; /* how many sysex bytes to write */
-} PmInternal;
-
-
-/* defined by system specific implementation, e.g. pmwinmm, used by PortMidi */
-void pm_init(void); 
-void pm_term(void); 
-
-/* defined by portMidi, used by pmwinmm */
-PmError none_write_short(PmInternal *midi, PmEvent *buffer);
-PmError none_write_byte(PmInternal *midi, unsigned char byte, 
-                        PmTimestamp timestamp);
-PmTimestamp none_synchronize(PmInternal *midi);
-
-PmError pm_fail_fn(PmInternal *midi);
-PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp);
-PmError pm_success_fn(PmInternal *midi);
-PmError pm_add_device(char *interf, char *name, int input, void *descriptor,
-                      pm_fns_type dictionary);
-unsigned int pm_read_bytes(PmInternal *midi, unsigned char *data, int len,
-                           PmTimestamp timestamp);
-void pm_read_short(PmInternal *midi, PmEvent *event);
-
-#define none_write_flush pm_fail_timestamp_fn
-#define none_sysex pm_fail_timestamp_fn
-#define none_poll pm_fail_fn
-#define success_poll pm_success_fn
-
-#define MIDI_REALTIME_MASK 0xf8
-#define is_real_time(msg) \
-    ((Pm_MessageStatus(msg) & MIDI_REALTIME_MASK) == MIDI_REALTIME_MASK)
-
-#ifdef __cplusplus
-}
-#endif
-
+/** @file pminternal.h header for PortMidi implementations */++/* this file is included by files that implement library internals */+/* Here is a guide to implementers:+     provide an initialization function similar to pm_winmm_init()+     add your initialization function to pm_init()+     Note that your init function should never require not-standard+         libraries or fail in any way. If the interface is not available,+         simply do not call pm_add_device. This means that non-standard+         libraries should try to do dynamic linking at runtime using a DLL+         and return without error if the DLL cannot be found or if there+         is any other failure.+     implement functions as indicated in pm_fns_type to open, read, write,+         close, etc.+     call pm_add_device() for each input and output device, passing it a+         pm_fns_type structure.+     assumptions about pm_fns_type functions are given below.+ */++/* add INTERNAL to Doxygen ENABLED_SECTIONS to include this: */+/** @cond INTERNAL */++#ifdef __cplusplus+extern "C" {+#endif++extern int pm_initialized; /* see note in portmidi.c */+extern PmDeviceID pm_default_input_device_id;+extern PmDeviceID pm_default_output_device_id;++/* these are defined in system-specific file */+void *pm_alloc(size_t s);+void pm_free(void *ptr);++/* if a host error (an error reported by the host MIDI API that is not+ * mapped to a PortMidi error code) occurs in a synchronous operation + * (i.e., not in a callback from another thread) set these: */+extern int pm_hosterror;  /* boolean */+extern char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN];+ +struct pm_internal_struct;++/* these do not use PmInternal because it is not defined yet... */+typedef PmError (*pm_write_short_fn)(struct pm_internal_struct *midi, +                                     PmEvent *buffer);+typedef PmError (*pm_begin_sysex_fn)(struct pm_internal_struct *midi,+                                     PmTimestamp timestamp);+typedef PmError (*pm_end_sysex_fn)(struct pm_internal_struct *midi,+                                   PmTimestamp timestamp);+typedef PmError (*pm_write_byte_fn)(struct pm_internal_struct *midi,+                                    unsigned char byte, PmTimestamp timestamp);+typedef PmError (*pm_write_realtime_fn)(struct pm_internal_struct *midi,+                                        PmEvent *buffer);+typedef PmError (*pm_write_flush_fn)(struct pm_internal_struct *midi,+                                     PmTimestamp timestamp);+typedef PmTimestamp (*pm_synchronize_fn)(struct pm_internal_struct *midi);+/* pm_open_fn should clean up all memory and close the device if any part+   of the open fails */+typedef PmError (*pm_open_fn)(struct pm_internal_struct *midi,+                              void *driverInfo);+typedef PmError (*pm_create_fn)(int is_input, const char *name,+                                void *driverInfo);+typedef PmError (*pm_delete_fn)(PmDeviceID id);+typedef PmError (*pm_abort_fn)(struct pm_internal_struct *midi);+/* pm_close_fn should clean up all memory and close the device if any+   part of the close fails. */+typedef PmError (*pm_close_fn)(struct pm_internal_struct *midi);+typedef PmError (*pm_poll_fn)(struct pm_internal_struct *midi);+typedef unsigned int (*pm_check_host_error_fn)(struct pm_internal_struct *midi);++typedef struct {+    pm_write_short_fn write_short; /* output short MIDI msg */+    pm_begin_sysex_fn begin_sysex; /* prepare to send a sysex message */+    pm_end_sysex_fn end_sysex; /* marks end of sysex message */+    pm_write_byte_fn write_byte; /* accumulate one more sysex byte */+    pm_write_realtime_fn write_realtime; /* send real-time msg within sysex */+    pm_write_flush_fn write_flush; /* send any accumulated but unsent data */+    pm_synchronize_fn synchronize; /* synchronize PM time to stream time */+    pm_open_fn open;   /* open MIDI device */+    pm_abort_fn abort; /* abort */+    pm_close_fn close; /* close device */+    pm_poll_fn poll;   /* read pending midi events into portmidi buffer */+    pm_check_host_error_fn check_host_error; /* true when device has had host */+          /* error; sets pm_hosterror and writes message to pm_hosterror_text */+} pm_fns_node, *pm_fns_type;+++/* when open fails, the dictionary gets this set of functions: */+extern pm_fns_node pm_none_dictionary;++typedef struct {+    PmDeviceInfo pub; /* some portmidi state also saved in here (for automatic+                         device closing -- see PmDeviceInfo struct) */+    int deleted; /* is this is a deleted virtual device? */+    void *descriptor; /* ID number passed to win32 multimedia API open, +                       * coreMIDI endpoint, etc., representing the device */+    struct pm_internal_struct *pm_internal; /* points to PmInternal device */+               /* when the device is open, allows automatic device closing */+    pm_fns_type dictionary;+} descriptor_node, *descriptor_type;++extern int pm_descriptor_max;+extern descriptor_type pm_descriptors;+extern int pm_descriptor_len;++typedef uint32_t (*time_get_proc_type)(void *time_info);++typedef struct pm_internal_struct {+    int device_id; /* which device is open (index to pm_descriptors) */+    short is_input; /* MIDI IN (true) or MIDI OUT (false) */+    short is_removed;  /* MIDI device was removed */+    PmTimeProcPtr time_proc; /* where to get the time */+    void *time_info; /* pass this to get_time() */+    int32_t buffer_len; /* how big is the buffer or queue? */+    PmQueue *queue;++    int32_t latency; /* time delay in ms between timestamps and actual output */+                  /* set to zero to get immediate, simple blocking output */+                  /* if latency is zero, timestamps will be ignored; */+                  /* if midi input device, this field ignored */+    +    int sysex_in_progress; /* when sysex status is seen, this flag becomes+        * true until EOX is seen. When true, new data is appended to the+        * stream of outgoing bytes. When overflow occurs, sysex data is +        * dropped (until an EOX or non-real-timei status byte is seen) so+        * that, if the overflow condition is cleared, we don't start +        * sending data from the middle of a sysex message. If a sysex+        * message is filtered, sysex_in_progress is false, causing the+        * message to be dropped. */+    PmMessage message; /* buffer for 4 bytes of sysex data */+    int message_count; /* how many bytes in sysex_message so far */+    int short_message_count; /* how many bytes are expected in short message */+    unsigned char running_status; /* running status byte or zero if none */+    int32_t filters; /* flags that filter incoming message classes */+    int32_t channel_mask; /* filter incoming messages based on channel */+    PmTimestamp last_msg_time; /* timestamp of last message */+    PmTimestamp sync_time; /* time of last synchronization */+    PmTimestamp now; /* set by PmWrite to current time */+    int first_message; /* initially true, used to run first synchronization */+    pm_fns_type dictionary; /* implementation functions */+    void *api_info; /* system-dependent state */+    /* the following are used to expedite sysex data */+    /* on windows, in debug mode, based on some profiling, these optimizations+     * cut the time to process sysex bytes from about 7.5 to 0.26 usec/byte,+     * but this does not count time in the driver, so I don't know if it is+     * important+     */+    unsigned char *fill_base; /* addr of ptr to sysex data */+    uint32_t *fill_offset_ptr; /* offset of next sysex byte */+    uint32_t fill_length; /* how many sysex bytes to write */+} PmInternal;++/* what is the length of this short message? */+int pm_midi_length(PmMessage msg);++/* defined by system specific implementation, e.g. pmwinmm, used by PortMidi */+void pm_init(void); +void pm_term(void); ++/* defined by portMidi, used by pmwinmm */+PmError none_write_short(PmInternal *midi, PmEvent *buffer);+PmError none_write_byte(PmInternal *midi, unsigned char byte, +                        PmTimestamp timestamp);+PmTimestamp none_synchronize(PmInternal *midi);++PmError pm_fail_fn(PmInternal *midi);+PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp);+PmError pm_success_fn(PmInternal *midi);+PmError pm_add_interf(const char *interf, pm_create_fn create_fn,+                      pm_delete_fn delete_fn);+PmError pm_add_device(const char *interf, const char *name, int is_input,+                      int is_virtual, void *descriptor, pm_fns_type dictionary);+void pm_undo_add_device(int id);+uint32_t pm_read_bytes(PmInternal *midi, const unsigned char *data, int len,+                           PmTimestamp timestamp);+void pm_read_short(PmInternal *midi, PmEvent *event);++#define none_write_flush pm_fail_timestamp_fn+#define none_sysex pm_fail_timestamp_fn+#define none_poll pm_fail_fn+#define success_poll pm_success_fn++#define MIDI_REALTIME_MASK 0xf8+#define is_real_time(msg) \+    ((Pm_MessageStatus(msg) & MIDI_REALTIME_MASK) == MIDI_REALTIME_MASK)++#ifdef __cplusplus+}+#endif++/** @endcond */
portmidi/pm_common/pmutil.c view
@@ -1,289 +1,283 @@-/* pmutil.c -- some helpful utilities for building midi
-               applications that use PortMidi
- */
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-#include "portmidi.h"
-#include "pmutil.h"
-#include "pminternal.h"
-
-#ifdef WIN32
-#define bzero(addr, siz) memset(addr, 0, siz)
-#endif
-
-// #define QUEUE_DEBUG 1
-#ifdef QUEUE_DEBUG
-#include "stdio.h"
-#endif
-
-/* code is based on 4-byte words -- it should work on a 64-bit machine
-   as long as a "long" has 4 bytes. This code could be generalized to
-   be independent of the size of "long" */
-
-typedef long int32;
-
-typedef struct {
-    long head;
-    long tail;
-    long len;
-    long msg_size; /* number of int32 in a message including extra word */
-    long overflow;
-    long peek_overflow;
-    int32 *buffer;
-    int32 *peek;
-    int peek_flag;
-} PmQueueRep;
-
-
-PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg)
-{
-    int int32s_per_msg = ((bytes_per_msg + sizeof(int32) - 1) &
-                          ~(sizeof(int32) - 1)) / sizeof(int32);
-    PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep));
-    if (!queue) /* memory allocation failed */
-        return NULL;
-
-    /* need extra word per message for non-zero encoding */
-    queue->len = num_msgs * (int32s_per_msg + 1);
-    queue->buffer = (int32 *) pm_alloc(queue->len * sizeof(int32));
-    bzero(queue->buffer, queue->len * sizeof(int32));
-    if (!queue->buffer) {
-        pm_free(queue);
-        return NULL;
-    } else { /* allocate the "peek" buffer */
-        queue->peek = (int32 *) pm_alloc(int32s_per_msg * sizeof(int32));
-        if (!queue->peek) {
-            /* free everything allocated so far and return */
-            pm_free(queue->buffer);
-            pm_free(queue);
-            return NULL;
-        }
-    }
-    bzero(queue->buffer, queue->len * sizeof(int32));
-    queue->head = 0;
-    queue->tail = 0;
-    /* msg_size is in words */
-    queue->msg_size = int32s_per_msg + 1; /* note extra word is counted */
-    queue->overflow = FALSE;
-    queue->peek_overflow = FALSE;
-    queue->peek_flag = FALSE;
-    return queue;
-}
-
-
-PmError Pm_QueueDestroy(PmQueue *q)
-{
-    PmQueueRep *queue = (PmQueueRep *) q;
-        
-    /* arg checking */
-    if (!queue || !queue->buffer || !queue->peek) 
-                return pmBadPtr;
-    
-    pm_free(queue->peek);
-    pm_free(queue->buffer);
-    pm_free(queue);
-    return pmNoError;
-}
-
-
-PmError Pm_Dequeue(PmQueue *q, void *msg)
-{
-    long head;
-    PmQueueRep *queue = (PmQueueRep *) q;
-    int i;
-    int32 *msg_as_int32 = (int32 *) msg;
-
-    /* arg checking */
-    if (!queue)
-        return pmBadPtr;
-    /* a previous peek operation encountered an overflow, but the overflow
-     * has not yet been reported to client, so do it now. No message is
-     * returned, but on the next call, we will return the peek buffer.
-     */
-    if (queue->peek_overflow) {
-        queue->peek_overflow = FALSE;
-        return pmBufferOverflow;
-    }
-    if (queue->peek_flag) {
-        memcpy(msg, queue->peek, (queue->msg_size - 1) * sizeof(int32));
-        queue->peek_flag = FALSE;
-        return pmGotData;
-    }
-
-    head = queue->head;
-    /* if writer overflows, it writes queue->overflow = tail+1 so that
-     * when the reader gets to that position in the buffer, it can 
-     * return the overflow condition to the reader. The problem is that
-     * at overflow, things have wrapped around, so tail == head, and the
-     * reader will detect overflow immediately instead of waiting until
-     * it reads everything in the buffer, wrapping around again to the
-     * point where tail == head. So the condition also checks that
-     * queue->buffer[head] is zero -- if so, then the buffer is now
-     * empty, and we're at the point in the msg stream where overflow
-     * occurred. It's time to signal overflow to the reader. If 
-     * queue->buffer[head] is non-zero, there's a message there and we
-     * should read all the way around the buffer before signalling overflow.
-     * There is a write-order dependency here, but to fail, the overflow
-     * field would have to be written while an entire buffer full of 
-     * writes are still pending. I'm assuming out-of-order writes are
-     * possible, but not that many.
-     */
-    if (queue->overflow == head + 1 && !queue->buffer[head]) {
-        queue->overflow = 0; /* non-overflow condition */
-        return pmBufferOverflow;
-    }
-
-    /* test to see if there is data in the queue -- test from back
-     * to front so if writer is simultaneously writing, we don't
-     * waste time discovering the write is not finished 
-     */
-    for (i = queue->msg_size - 1; i >= 0; i--) {
-        if (!queue->buffer[head + i]) {
-            return pmNoData;
-        }
-    }
-    memcpy(msg, (char *) &queue->buffer[head + 1], 
-           sizeof(int32) * (queue->msg_size - 1));
-    /* fix up zeros */
-    i = queue->buffer[head];
-    while (i < queue->msg_size) {
-        int32 j;
-        i--; /* msg does not have extra word so shift down */
-        j = msg_as_int32[i];
-        msg_as_int32[i] = 0;
-        i = j;
-    }
-    /* signal that data has been removed by zeroing: */
-    bzero((char *) &queue->buffer[head], sizeof(int32) * queue->msg_size);
-
-    /* update head */
-    head += queue->msg_size;
-    if (head == queue->len) head = 0;
-    queue->head = head;
-    return pmGotData; /* success */
-}
-
-
-
-PmError Pm_SetOverflow(PmQueue *q)
-{
-    PmQueueRep *queue = (PmQueueRep *) q;
-    long tail;
-    /* arg checking */
-    if (!queue)
-        return pmBadPtr;
-    /* no more enqueue until receiver acknowledges overflow */
-    if (queue->overflow) return pmBufferOverflow;
-    tail = queue->tail;
-    queue->overflow = tail + 1;
-    return pmBufferOverflow;
-}
-
-
-PmError Pm_Enqueue(PmQueue *q, void *msg)
-{
-    PmQueueRep *queue = (PmQueueRep *) q;
-    long tail;
-    int i;
-    int32 *src = (int32 *) msg;
-    int32 *ptr;
-    int32 *dest;
-    int rslt;
-    if (!queue) 
-        return pmBadPtr;
-    /* no more enqueue until receiver acknowledges overflow */
-    if (queue->overflow) return pmBufferOverflow;
-    rslt = Pm_QueueFull(q);
-    /* already checked above: if (rslt == pmBadPtr) return rslt; */
-    tail = queue->tail;
-    if (rslt) {
-        queue->overflow = tail + 1;
-        return pmBufferOverflow;
-    }
-
-    /* queue is has room for message, and overflow flag is cleared */
-    ptr = &queue->buffer[tail];
-    dest = ptr + 1;
-    for (i = 1; i < queue->msg_size; i++) {
-        int32 j = src[i - 1];
-        if (!j) {
-            *ptr = i;
-            ptr = dest;
-        } else {
-            *dest = j;
-        }
-        dest++;
-    }
-    *ptr = i;
-    tail += queue->msg_size;
-    if (tail == queue->len) tail = 0;
-    queue->tail = tail;
-    return pmNoError;
-}
-
-
-int Pm_QueueEmpty(PmQueue *q)
-{
-    PmQueueRep *queue = (PmQueueRep *) q;
-    return (!queue) ||  /* null pointer -> return "empty" */
-           (queue->buffer[queue->head] == 0 && !queue->peek_flag);
-}
-
-
-int Pm_QueueFull(PmQueue *q)
-{
-    int tail;
-    int i; 
-    PmQueueRep *queue = (PmQueueRep *) q;
-    /* arg checking */
-    if (!queue)
-        return pmBadPtr;
-    tail = queue->tail;
-    /* test to see if there is space in the queue */
-    for (i = 0; i < queue->msg_size; i++) {
-        if (queue->buffer[tail + i]) {
-            return TRUE;
-        }
-    }
-    return FALSE;
-}
-
-
-void *Pm_QueuePeek(PmQueue *q)
-{
-    PmError rslt;
-    long temp;
-    PmQueueRep *queue = (PmQueueRep *) q;
-    /* arg checking */
-    if (!queue)
-        return NULL;
-
-    if (queue->peek_flag) {
-        return queue->peek;
-    }
-    /* this is ugly: if peek_overflow is set, then Pm_Dequeue() 
-     * returns immediately with pmBufferOverflow, but here, we
-     * want Pm_Dequeue() to really check for data. If data is
-     * there, we can return it
-     */
-    temp = queue->peek_overflow;
-    queue->peek_overflow = FALSE;
-    rslt = Pm_Dequeue(q, queue->peek);
-    queue->peek_overflow = temp;
-
-    if (rslt == 1) {
-        queue->peek_flag = TRUE;
-        return queue->peek;
-    } else if (rslt == pmBufferOverflow) {
-        /* when overflow is indicated, the queue is empty and the 
-         * first message that was dropped by Enqueue (signalling
-         * pmBufferOverflow to its caller) would have been the next
-         * message in the queue. Pm_QueuePeek will return NULL, but
-         * remember that an overflow occurred. (see Pm_Dequeue)
-         */
-        queue->peek_overflow = TRUE;
-    }
-    return NULL;
-}
-
+/* pmutil.c -- some helpful utilities for building midi+               applications that use PortMidi+ */+#include <stdlib.h>+#include <assert.h>+#include <string.h>+#include "portmidi.h"+#include "pmutil.h"+#include "pminternal.h"++#ifdef WIN32+#define bzero(addr, siz) memset(addr, 0, siz)+#endif++/* #define QUEUE_DEBUG 1 */+#ifdef QUEUE_DEBUG+#include "stdio.h"+#endif++typedef struct {+    long head;+    long tail;+    long len;+    long overflow;+    int32_t msg_size; /* number of int32_t in a message including extra word */+    int32_t peek_overflow;+    int32_t *buffer;+    int32_t *peek;+    int32_t peek_flag;+} PmQueueRep;+++PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg)+{+    int32_t int32s_per_msg = +            (int32_t) (((bytes_per_msg + sizeof(int32_t) - 1) &+                       ~(sizeof(int32_t) - 1)) / sizeof(int32_t));+    PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep));+    if (!queue) /* memory allocation failed */+        return NULL;++    /* need extra word per message for non-zero encoding */+    queue->len = num_msgs * (int32s_per_msg + 1);+    queue->buffer = (int32_t *) pm_alloc(queue->len * sizeof(int32_t));+    if (!queue->buffer) {+        pm_free(queue);+        return NULL;+    } else { /* allocate the "peek" buffer */+        queue->peek = (int32_t *) pm_alloc(int32s_per_msg * sizeof(int32_t));+        if (!queue->peek) {+            /* free everything allocated so far and return */+            pm_free(queue->buffer);+            pm_free(queue);+            return NULL;+        }+    }+    bzero(queue->buffer, queue->len * sizeof(int32_t));+    queue->head = 0;+    queue->tail = 0;+    /* msg_size is in words */+    queue->msg_size = int32s_per_msg + 1; /* note extra word is counted */+    queue->overflow = FALSE;+    queue->peek_overflow = FALSE;+    queue->peek_flag = FALSE;+    return queue;+}+++PMEXPORT PmError Pm_QueueDestroy(PmQueue *q)+{+    PmQueueRep *queue = (PmQueueRep *) q;+        +    /* arg checking */+    if (!queue || !queue->buffer || !queue->peek) +                return pmBadPtr;+    +    pm_free(queue->peek);+    pm_free(queue->buffer);+    pm_free(queue);+    return pmNoError;+}+++PMEXPORT PmError Pm_Dequeue(PmQueue *q, void *msg)+{+    long head;+    PmQueueRep *queue = (PmQueueRep *) q;+    int i;+    int32_t *msg_as_int32 = (int32_t *) msg;++    /* arg checking */+    if (!queue)+        return pmBadPtr;+    /* a previous peek operation encountered an overflow, but the overflow+     * has not yet been reported to client, so do it now. No message is+     * returned, but on the next call, we will return the peek buffer.+     */+    if (queue->peek_overflow) {+        queue->peek_overflow = FALSE;+        return pmBufferOverflow;+    }+    if (queue->peek_flag) {+        memcpy(msg, queue->peek, (queue->msg_size - 1) * sizeof(int32_t));+        queue->peek_flag = FALSE;+        return pmGotData;+    }++    head = queue->head;+    /* if writer overflows, it writes queue->overflow = tail+1 so that+     * when the reader gets to that position in the buffer, it can +     * return the overflow condition to the reader. The problem is that+     * at overflow, things have wrapped around, so tail == head, and the+     * reader will detect overflow immediately instead of waiting until+     * it reads everything in the buffer, wrapping around again to the+     * point where tail == head. So the condition also checks that+     * queue->buffer[head] is zero -- if so, then the buffer is now+     * empty, and we're at the point in the msg stream where overflow+     * occurred. It's time to signal overflow to the reader. If +     * queue->buffer[head] is non-zero, there's a message there and we+     * should read all the way around the buffer before signalling overflow.+     * There is a write-order dependency here, but to fail, the overflow+     * field would have to be written while an entire buffer full of +     * writes are still pending. I'm assuming out-of-order writes are+     * possible, but not that many.+     */+    if (queue->overflow == head + 1 && !queue->buffer[head]) {+        queue->overflow = 0; /* non-overflow condition */+        return pmBufferOverflow;+    }++    /* test to see if there is data in the queue -- test from back+     * to front so if writer is simultaneously writing, we don't+     * waste time discovering the write is not finished +     */+    for (i = queue->msg_size - 1; i >= 0; i--) {+        if (!queue->buffer[head + i]) {+            return pmNoData;+        }+    }+    memcpy(msg, (char *) &queue->buffer[head + 1], +           sizeof(int32_t) * (queue->msg_size - 1));+    /* fix up zeros */+    i = queue->buffer[head];+    while (i < queue->msg_size) {+        int32_t j;+        i--; /* msg does not have extra word so shift down */+        j = msg_as_int32[i];+        msg_as_int32[i] = 0;+        i = j;+    }+    /* signal that data has been removed by zeroing: */+    bzero((char *) &queue->buffer[head], sizeof(int32_t) * queue->msg_size);++    /* update head */+    head += queue->msg_size;+    if (head == queue->len) head = 0;+    queue->head = head;+    return pmGotData; /* success */+}++++PMEXPORT PmError Pm_SetOverflow(PmQueue *q)+{+    PmQueueRep *queue = (PmQueueRep *) q;+    long tail;+    /* arg checking */+    if (!queue)+        return pmBadPtr;+    /* no more enqueue until receiver acknowledges overflow */+    if (queue->overflow) return pmBufferOverflow;+    tail = queue->tail;+    queue->overflow = tail + 1;+    return pmBufferOverflow;+}+++PMEXPORT PmError Pm_Enqueue(PmQueue *q, void *msg)+{+    PmQueueRep *queue = (PmQueueRep *) q;+    long tail;+    int i;+    int32_t *src = (int32_t *) msg;+    int32_t *ptr;+    int32_t *dest;+    int rslt;+    if (!queue) +        return pmBadPtr;+    /* no more enqueue until receiver acknowledges overflow */+    if (queue->overflow) return pmBufferOverflow;+    rslt = Pm_QueueFull(q);+    /* already checked above: if (rslt == pmBadPtr) return rslt; */+    tail = queue->tail;+    if (rslt) {+        queue->overflow = tail + 1;+        return pmBufferOverflow;+    }++    /* queue is has room for message, and overflow flag is cleared */+    ptr = &queue->buffer[tail];+    dest = ptr + 1;+    for (i = 1; i < queue->msg_size; i++) {+        int32_t j = src[i - 1];+        if (!j) {+            *ptr = i;+            ptr = dest;+        } else {+            *dest = j;+        }+        dest++;+    }+    *ptr = i;+    tail += queue->msg_size;+    if (tail == queue->len) tail = 0;+    queue->tail = tail;+    return pmNoError;+}+++PMEXPORT int Pm_QueueEmpty(PmQueue *q)+{+    PmQueueRep *queue = (PmQueueRep *) q;+    return (!queue) ||  /* null pointer -> return "empty" */+           (queue->buffer[queue->head] == 0 && !queue->peek_flag);+}+++PMEXPORT int Pm_QueueFull(PmQueue *q)+{+    long tail;+    int i; +    PmQueueRep *queue = (PmQueueRep *) q;+    /* arg checking */+    if (!queue)+        return pmBadPtr;+    tail = queue->tail;+    /* test to see if there is space in the queue */+    for (i = 0; i < queue->msg_size; i++) {+        if (queue->buffer[tail + i]) {+            return TRUE;+        }+    }+    return FALSE;+}+++PMEXPORT void *Pm_QueuePeek(PmQueue *q)+{+    PmError rslt;+    int32_t temp;+    PmQueueRep *queue = (PmQueueRep *) q;+    /* arg checking */+    if (!queue)+        return NULL;++    if (queue->peek_flag) {+        return queue->peek;+    }+    /* this is ugly: if peek_overflow is set, then Pm_Dequeue() +     * returns immediately with pmBufferOverflow, but here, we+     * want Pm_Dequeue() to really check for data. If data is+     * there, we can return it+     */+    temp = queue->peek_overflow;+    queue->peek_overflow = FALSE;+    rslt = Pm_Dequeue(q, queue->peek);+    queue->peek_overflow = temp;++    if (rslt == 1) {+        queue->peek_flag = TRUE;+        return queue->peek;+    } else if (rslt == pmBufferOverflow) {+        /* when overflow is indicated, the queue is empty and the +         * first message that was dropped by Enqueue (signalling+         * pmBufferOverflow to its caller) would have been the next+         * message in the queue. Pm_QueuePeek will return NULL, but+         * remember that an overflow occurred. (see Pm_Dequeue)+         */+        queue->peek_overflow = TRUE;+    }+    return NULL;+}+
portmidi/pm_common/pmutil.h view
@@ -1,127 +1,184 @@-/* pmutil.h -- some helpful utilities for building midi 
-               applications that use PortMidi 
- */
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-typedef void PmQueue;
-
-/*
-    A single-reader, single-writer queue is created by
-    Pm_QueueCreate(), which takes the number of messages and
-    the message size as parameters. The queue only accepts
-    fixed sized messages. Returns NULL if memory cannot be allocated.
-
-    This queue implementation uses the "light pipe" algorithm which
-    operates correctly even with multi-processors and out-of-order
-    memory writes. (see Alexander Dokumentov, "Lock-free Interprocess
-    Communication," Dr. Dobbs Portal, http://www.ddj.com/, 
-    articleID=189401457, June 15, 2006. This algorithm requires
-    that messages be translated to a form where no words contain
-    zeros. Each word becomes its own "data valid" tag. Because of
-    this translation, we cannot return a pointer to data still in 
-    the queue when the "peek" method is called. Instead, a buffer 
-    is preallocated so that data can be copied there. Pm_QueuePeek() 
-    dequeues a message into this buffer and returns a pointer to 
-    it. A subsequent Pm_Dequeue() will copy from this buffer.
-
-    This implementation does not try to keep reader/writer data in
-    separate cache lines or prevent thrashing on cache lines. 
-    However, this algorithm differs by doing inserts/removals in
-    units of messages rather than units of machine words. Some
-    performance improvement might be obtained by not clearing data
-    immediately after a read, but instead by waiting for the end
-    of the cache line, especially if messages are smaller than
-    cache lines. See the Dokumentov article for explanation.
-
-    The algorithm is extended to handle "overflow" reporting. To report
-    an overflow, the sender writes the current tail position to a field.
-    The receiver must acknowlege receipt by zeroing the field. The sender
-    will not send more until the field is zeroed.
-    
-    Pm_QueueDestroy() destroys the queue and frees its storage.
- */
-
-PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg);
-PmError Pm_QueueDestroy(PmQueue *queue);
-
-/* 
-    Pm_Dequeue() removes one item from the queue, copying it into msg.
-    Returns 1 if successful, and 0 if the queue is empty.
-    Returns pmBufferOverflow if what would have been the next thing
-    in the queue was dropped due to overflow. (So when overflow occurs,
-    the receiver can receive a queue full of messages before getting the
-    overflow report. This protocol ensures that the reader will be 
-    notified when data is lost due to overflow.
- */
-PmError Pm_Dequeue(PmQueue *queue, void *msg);
-
-
-/*
-    Pm_Enqueue() inserts one item into the queue, copying it from msg.
-    Returns pmNoError if successful and pmBufferOverflow if the queue was 
-    already full. If pmBufferOverflow is returned, the overflow flag is set.
- */
-PmError Pm_Enqueue(PmQueue *queue, void *msg);
-
-
-/*
-    Pm_QueueFull() returns non-zero if the queue is full
-    Pm_QueueEmpty() returns non-zero if the queue is empty
-
-    Either condition may change immediately because a parallel
-    enqueue or dequeue operation could be in progress. Furthermore,
-    Pm_QueueEmpty() is optimistic: it may say false, when due to 
-    out-of-order writes, the full message has not arrived. Therefore,
-    Pm_Dequeue() could still return 0 after Pm_QueueEmpty() returns
-    false. On the other hand, Pm_QueueFull() is pessimistic: if it
-    returns false, then Pm_Enqueue() is guaranteed to succeed. 
-
-    Error conditions: Pm_QueueFull() returns pmBadPtr if queue is NULL.
-    Pm_QueueEmpty() returns FALSE if queue is NULL.
- */
-int Pm_QueueFull(PmQueue *queue);
-int Pm_QueueEmpty(PmQueue *queue);
-
-
-/*
-    Pm_QueuePeek() returns a pointer to the item at the head of the queue,
-    or NULL if the queue is empty. The item is not removed from the queue.
-    Pm_QueuePeek() will not indicate when an overflow occurs. If you want
-    to get and check pmBufferOverflow messages, use the return value of
-    Pm_QueuePeek() *only* as an indication that you should call 
-    Pm_Dequeue(). At the point where a direct call to Pm_Dequeue() would
-    return pmBufferOverflow, Pm_QueuePeek() will return NULL but internally
-    clear the pmBufferOverflow flag, enabling Pm_Enqueue() to resume
-    enqueuing messages. A subsequent call to Pm_QueuePeek()
-    will return a pointer to the first message *after* the overflow. 
-    Using this as an indication to call Pm_Dequeue(), the first call
-    to Pm_Dequeue() will return pmBufferOverflow. The second call will
-    return success, copying the same message pointed to by the previous
-    Pm_QueuePeek().
-
-    When to use Pm_QueuePeek(): (1) when you need to look at the message
-    data to decide who should be called to receive it. (2) when you need
-    to know a message is ready but cannot accept the message.
-
-    Note that Pm_QueuePeek() is not a fast check, so if possible, you 
-    might as well just call Pm_Dequeue() and accept the data if it is there.
- */
-void *Pm_QueuePeek(PmQueue *queue);
-
-/*
-    Pm_SetOverflow() allows the writer (enqueuer) to signal an overflow
-    condition to the reader (dequeuer). E.g. when transfering data from 
-    the OS to an application, if the OS indicates a buffer overrun,
-    Pm_SetOverflow() can be used to insure that the reader receives a
-    pmBufferOverflow result from Pm_Dequeue(). Returns pmBadPtr if queue
-    is NULL, returns pmBufferOverflow if buffer is already in an overflow
-    state, returns pmNoError if successfully set overflow state.
- */
-PmError Pm_SetOverflow(PmQueue *queue);
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
+/** @file pmutil.h lock-free queue for building MIDI +                   applications with PortMidi. ++    PortMidi is not reentrant, and locks can suffer from priority+    inversion.  To support coordination between system callbacks, a+    high-priority thread created with PortTime, and the main+    application thread, PortMidi uses a lock-free, non-blocking+    queue. The queue implementation is not particular to MIDI and is+    available for other uses.+ */++#ifndef PORTMIDI_PMUTIL_H+#define PORTMIDI_PMUTIL_H++#ifdef __cplusplus+extern "C" {+#endif /* __cplusplus */++/** @defgroup grp_pmutil Lock-free Queue+    @{+*/++/** The queue representation is opaque. Declare a queue as PmQueue * */+typedef void PmQueue;++/** create a single-reader, single-writer queue. ++    @param num_msgs the number of messages the queue can hold++    @param bytes_per_msg the fixed message size++    @return the allocated and initialized queue, or NULL if memory+    cannot be allocated. Allocation uses pm_alloc().++    The queue only accepts fixed sized messages. ++    This queue implementation uses the "light pipe" algorithm which+    operates correctly even with multi-processors and out-of-order+    memory writes. (see Alexander Dokumentov, "Lock-free Interprocess+    Communication," Dr. Dobbs Portal, http://www.ddj.com/,+    articleID=189401457, June 15, 2006. This algorithm requires that+    messages be translated to a form where no words contain+    zeros. Each word becomes its own "data valid" tag. Because of this+    translation, we cannot return a pointer to data still in the queue+    when the "peek" method is called. Instead, a buffer is+    preallocated so that data can be copied there. Pm_QueuePeek()+    dequeues a message into this buffer and returns a pointer to it. A+    subsequent Pm_Dequeue() will copy from this buffer.++    This implementation does not try to keep reader/writer data in+    separate cache lines or prevent thrashing on cache lines. +    However, this algorithm differs by doing inserts/removals in+    units of messages rather than units of machine words. Some+    performance improvement might be obtained by not clearing data+    immediately after a read, but instead by waiting for the end+    of the cache line, especially if messages are smaller than+    cache lines. See the Dokumentov article for explanation.++    The algorithm is extended to handle "overflow" reporting. To+    report an overflow, the sender writes the current tail position to+    a field.  The receiver must acknowlege receipt by zeroing the+    field. The sender will not send more until the field is zeroed.+ */+PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg);+    +/** destroy a queue and free its storage. ++    @param queue a queue created by #Pm_QueueCreate().++    @return pmNoError or an error code.++    Uses pm_free(). ++ */+PMEXPORT PmError Pm_QueueDestroy(PmQueue *queue);++/** remove one message from the queue, copying it into \p msg.++    @param queue a queue created by #Pm_QueueCreate().++    @param msg address to which the message, if any, is copied.++    @return 1 if successful, and 0 if the queue is empty.  Returns+    #pmBufferOverflow if what would have been the next thing in the+    queue was dropped due to overflow. (So when overflow occurs, the+    receiver can receive a queue full of messages before getting the+    overflow report. This protocol ensures that the reader will be+    notified when data is lost due to overflow.+ */+PMEXPORT PmError Pm_Dequeue(PmQueue *queue, void *msg);++/** insert one message into the queue, copying it from \p msg.++    @param queue a queue created by #Pm_QueueCreate().++    @param msg address of the message to be enqueued.++    @return #pmNoError if successful and #pmBufferOverflow if the+    queue was already full. If #pmBufferOverflow is returned, the+    overflow flag is set.+ */+PMEXPORT PmError Pm_Enqueue(PmQueue *queue, void *msg);++/** test if the queue is full.++    @param queue a queue created by #Pm_QueueCreate().++    @return non-zero iff the queue is empty, and #pmBadPtr if \p queue+    is NULL.++    The full condition may change immediately because a parallel+    dequeue operation could be in progress.  The result is+    pessimistic: if it returns false (zero) to the single writer, then+    #Pm_Enqueue() is guaranteed to succeed.+ */+PMEXPORT int Pm_QueueFull(PmQueue *queue);++/** test if the queue is empty.++    @param queue a queue created by #Pm_QueueCreate().++    @return zero iff the queue is either empty or NULL.++    The empty condition may change immediately because a parallel+    enqueue operation could be in progress. Furthermore, the +    result is optimistic: it may say false, when due to +    out-of-order writes, the full message has not arrived. Therefore,+    #Pm_Dequeue() could still return 0 after #Pm_QueueEmpty() returns+    false.+*/+PMEXPORT int Pm_QueueEmpty(PmQueue *queue);++/** get a pointer to the item at the head of the queue.++    @param queue a queue created by #Pm_QueueCreate().++    @result a pointer to the head message or NULL if the queue is empty.++    The message is not removed from the queue.  #Pm_QueuePeek() will+    not indicate when an overflow occurs. If you want to get and check+    #pmBufferOverflow messages, use the return value of+    #Pm_QueuePeek() *only* as an indication that you should call+    #Pm_Dequeue(). At the point where a direct call to #Pm_Dequeue()+    would return #pmBufferOverflow, #Pm_QueuePeek() will return NULL,+    but internally clear the #pmBufferOverflow flag, enabling+    #Pm_Enqueue() to resume enqueuing messages. A subsequent call to+    #Pm_QueuePeek() will return a pointer to the first message *after*+    the overflow.  Using this as an indication to call #Pm_Dequeue(),+    the first call to #Pm_Dequeue() will return #pmBufferOverflow. The+    second call will return success, copying the same message pointed+    to by the previous #Pm_QueuePeek().++    When to use #Pm_QueuePeek(): (1) when you need to look at the message+    data to decide who should be called to receive it. (2) when you need+    to know a message is ready but cannot accept the message.++    Note that #Pm_QueuePeek() is not a fast check, so if possible, you +    might as well just call #Pm_Dequeue() and accept the data if it is there.+ */+PMEXPORT void *Pm_QueuePeek(PmQueue *queue);++/** allows the writer (enqueuer) to signal an overflow+    condition to the reader (dequeuer). ++    @param queue a queue created by #Pm_QueueCreate().++    @return #pmNoError if overflow is set, or #pmBadPtr if queue is+    NULL, or #pmBufferOverflow if buffer is already in an overflow+    state.++    E.g., when transfering data from the OS to an application, if the+    OS indicates a buffer overrun, #Pm_SetOverflow() can be used to+    insure that the reader receives a #pmBufferOverflow result from+    #Pm_Dequeue().+ */+PMEXPORT PmError Pm_SetOverflow(PmQueue *queue);++/** @} */++#ifdef __cplusplus+}+#endif /* __cplusplus */++#endif /* PORTMIDI_PMUTIL_H */
portmidi/pm_common/portmidi.c view
@@ -1,1084 +1,1477 @@-#include "stdlib.h"
-#include "string.h"
-#include "portmidi.h"
-#include "porttime.h"
-#include "pmutil.h"
-#include "pminternal.h"
-#include <assert.h>
-
-#define MIDI_CLOCK      0xf8
-#define MIDI_ACTIVE     0xfe
-#define MIDI_STATUS_MASK 0x80
-#define MIDI_SYSEX      0xf0
-#define MIDI_EOX        0xf7
-#define MIDI_START      0xFA
-#define MIDI_STOP       0xFC
-#define MIDI_CONTINUE   0xFB
-#define MIDI_F9         0xF9
-#define MIDI_FD         0xFD
-#define MIDI_RESET      0xFF
-#define MIDI_NOTE_ON    0x90
-#define MIDI_NOTE_OFF   0x80
-#define MIDI_CHANNEL_AT 0xD0
-#define MIDI_POLY_AT    0xA0
-#define MIDI_PROGRAM    0xC0
-#define MIDI_CONTROL    0xB0
-#define MIDI_PITCHBEND  0xE0
-#define MIDI_MTC        0xF1
-#define MIDI_SONGPOS    0xF2
-#define MIDI_SONGSEL    0xF3
-#define MIDI_TUNE       0xF6
-
-#define is_empty(midi) ((midi)->tail == (midi)->head)
-
-static int pm_initialized = FALSE;
-int pm_hosterror;
-char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN];
-
-#ifdef PM_CHECK_ERRORS
-
-#include <stdio.h>
-
-#define STRING_MAX 80
-
-static void prompt_and_exit(void)
-{
-    char line[STRING_MAX];
-    printf("type ENTER...");
-    fgets(line, STRING_MAX, stdin);
-    /* this will clean up open ports: */
-    exit(-1);
-}
-
-
-static PmError pm_errmsg(PmError err)
-{
-    if (err == pmHostError) {
-        /* it seems pointless to allocate memory and copy the string,
-         * so I will do the work of Pm_GetHostErrorText directly
-         */
-        printf("PortMidi found host error...\n  %s\n", pm_hosterror_text);
-        pm_hosterror = FALSE;
-        pm_hosterror_text[0] = 0; /* clear the message */
-        prompt_and_exit();
-    } else if (err < 0) {
-        printf("PortMidi call failed...\n  %s\n", Pm_GetErrorText(err));
-        prompt_and_exit();
-    }
-    return err;
-}
-#else
-#define pm_errmsg(err) err
-#endif
-
-/*
-====================================================================
-system implementation of portmidi interface
-====================================================================
-*/
-
-int pm_descriptor_max = 0;
-int pm_descriptor_index = 0;
-descriptor_type descriptors = NULL;
-
-/* pm_add_device -- describe interface/device pair to library 
- *
- * This is called at intialization time, once for each 
- * interface (e.g. DirectSound) and device (e.g. SoundBlaster 1)
- * The strings are retained but NOT COPIED, so do not destroy them!
- *
- * returns pmInvalidDeviceId if device memory is exceeded
- * otherwise returns pmNoError
- */
-PmError pm_add_device(char *interf, char *name, int input, 
-                      void *descriptor, pm_fns_type dictionary) {
-    if (pm_descriptor_index >= pm_descriptor_max) {
-        // expand descriptors
-        descriptor_type new_descriptors = (descriptor_type) 
-            pm_alloc(sizeof(descriptor_node) * (pm_descriptor_max + 32));
-        if (!new_descriptors) return pmInsufficientMemory;
-        if (descriptors) {
-            memcpy(new_descriptors, descriptors, 
-                   sizeof(descriptor_node) * pm_descriptor_max);
-            free(descriptors);
-        }
-        pm_descriptor_max += 32;
-        descriptors = new_descriptors;
-    }
-    descriptors[pm_descriptor_index].pub.interf = interf;
-    descriptors[pm_descriptor_index].pub.name = name;
-    descriptors[pm_descriptor_index].pub.input = input;
-    descriptors[pm_descriptor_index].pub.output = !input;
-
-    /* default state: nothing to close (for automatic device closing) */
-    descriptors[pm_descriptor_index].pub.opened = FALSE;
-
-    /* ID number passed to win32 multimedia API open */
-    descriptors[pm_descriptor_index].descriptor = descriptor;
-    
-    /* points to PmInternal, allows automatic device closing */
-    descriptors[pm_descriptor_index].internalDescriptor = NULL;
-
-    descriptors[pm_descriptor_index].dictionary = dictionary;
-    
-    pm_descriptor_index++;
-    
-    return pmNoError;
-}
-
-
-/*
-====================================================================
-portmidi implementation
-====================================================================
-*/
-
-int Pm_CountDevices( void ) {
-    Pm_Initialize();
-    /* no error checking -- Pm_Initialize() does not fail */
-    return pm_descriptor_index;
-}
-
-
-const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ) {
-    Pm_Initialize(); /* no error check needed */
-    if (id >= 0 && id < pm_descriptor_index) {
-        return &descriptors[id].pub;
-    }
-    return NULL;
-}
-
-/* pm_success_fn -- "noop" function pointer */
-PmError pm_success_fn(PmInternal *midi) {
-    return pmNoError;
-}
-
-/* none_write -- returns an error if called */
-PmError none_write_short(PmInternal *midi, PmEvent *buffer) {
-    return pmBadPtr;
-}
-
-/* pm_fail_timestamp_fn -- placeholder for begin_sysex and flush */
-PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp) {
-    return pmBadPtr;
-}
-
-PmError none_write_byte(PmInternal *midi, unsigned char byte, 
-                        PmTimestamp timestamp) {
-    return pmBadPtr;
-}
-
-/* pm_fail_fn -- generic function, returns error if called */
-PmError pm_fail_fn(PmInternal *midi) {
-    return pmBadPtr;
-}
-
-static PmError none_open(PmInternal *midi, void *driverInfo) {
-    return pmBadPtr;
-}
-static void none_get_host_error(PmInternal * midi, char * msg, unsigned int len) {
-    strcpy(msg, "");
-}
-static unsigned int none_has_host_error(PmInternal * midi) {
-    return FALSE;
-}
-PmTimestamp none_synchronize(PmInternal *midi) {
-    return 0;
-}
-
-#define none_abort pm_fail_fn
-#define none_close pm_fail_fn
-
-pm_fns_node pm_none_dictionary = {
-    none_write_short,
-    none_sysex,
-    none_sysex,
-    none_write_byte,
-    none_write_short,
-    none_write_flush,
-    none_synchronize,
-    none_open,
-    none_abort, 
-    none_close,
-    none_poll,
-    none_has_host_error,
-    none_get_host_error 
-};
-
-
-const char *Pm_GetErrorText( PmError errnum ) {
-    const char *msg;
-
-    switch(errnum)
-    {
-    case pmNoError:                  
-        msg = ""; 
-        break;
-    case pmHostError:                
-        msg = "PortMidi: `Host error'"; 
-        break;
-    case pmInvalidDeviceId:          
-        msg = "PortMidi: `Invalid device ID'"; 
-        break;
-    case pmInsufficientMemory:       
-        msg = "PortMidi: `Insufficient memory'"; 
-        break;
-    case pmBufferTooSmall:           
-        msg = "PortMidi: `Buffer too small'"; 
-        break;
-    case pmBadPtr:                   
-        msg = "PortMidi: `Bad pointer'"; 
-        break;
-    case pmInternalError:            
-        msg = "PortMidi: `Internal PortMidi Error'"; 
-        break;
-    case pmBufferOverflow:
-        msg = "PortMidi: `Buffer overflow'";
-        break;
-    case pmBadData:
-        msg = "PortMidi: `Invalid MIDI message Data'";
-        break;
-    case pmBufferMaxSize:
-        msg = "PortMidi: `Buffer cannot be made larger'";
-        break;
-    default:                         
-        msg = "PortMidi: `Illegal error number'"; 
-        break;
-    }
-    return msg;
-}
-
-
-/* This can be called whenever you get a pmHostError return value.
- * The error will always be in the global pm_hosterror_text.
- */
-void Pm_GetHostErrorText(char * msg, unsigned int len) {
-    assert(msg);
-    assert(len > 0);
-    if (pm_hosterror) {
-        strncpy(msg, (char *) pm_hosterror_text, len);
-        pm_hosterror = FALSE;
-        pm_hosterror_text[0] = 0; /* clear the message; not necessary, but it
-                                     might help with debugging */
-        msg[len - 1] = 0; /* make sure string is terminated */
-    } else {
-        msg[0] = 0; /* no string to return */
-    }
-}
-
-
-int Pm_HasHostError(PortMidiStream * stream) {
-    if (pm_hosterror) return TRUE;
-    if (stream) {
-        PmInternal * midi = (PmInternal *) stream;
-        pm_hosterror = (*midi->dictionary->has_host_error)(midi);
-        if (pm_hosterror) {
-            midi->dictionary->host_error(midi, pm_hosterror_text, 
-                                         PM_HOST_ERROR_MSG_LEN);
-            /* now error message is global */
-            return TRUE;
-        }
-    }
-    return FALSE;
-}
-
-
-PmError Pm_Initialize( void ) {
-    if (!pm_initialized) {
-        pm_hosterror = FALSE;
-        pm_hosterror_text[0] = 0; /* the null string */
-        pm_init();
-        pm_initialized = TRUE;
-    }
-    return pmNoError;
-}
-
-
-PmError Pm_Terminate( void ) {
-    if (pm_initialized) {
-        pm_term();
-        // if there are no devices, descriptors might still be NULL
-        if (descriptors != NULL) {
-            free(descriptors);
-            descriptors = NULL;
-        }
-        pm_descriptor_index = 0;
-        pm_descriptor_max = 0;
-        pm_initialized = FALSE;
-    }
-    return pmNoError;
-}
-
-
-/* Pm_Read -- read up to length longs from source into buffer */
-/*
- * returns number of longs actually read, or error code
- */
-int Pm_Read(PortMidiStream *stream, PmEvent *buffer, long length) {
-    PmInternal *midi = (PmInternal *) stream;
-    int n = 0;
-    PmError err = pmNoError;
-    pm_hosterror = FALSE;
-    /* arg checking */
-    if(midi == NULL)
-        err = pmBadPtr;
-    else if(!descriptors[midi->device_id].pub.opened)
-        err = pmBadPtr;
-    else if(!descriptors[midi->device_id].pub.input)
-        err = pmBadPtr;    
-    /* First poll for data in the buffer...
-     * This either simply checks for data, or attempts first to fill the buffer
-     * with data from the MIDI hardware; this depends on the implementation.
-     * We could call Pm_Poll here, but that would redo a lot of redundant
-     * parameter checking, so I copied some code from Pm_Poll to here: */
-    else err = (*(midi->dictionary->poll))(midi);
-
-    if (err != pmNoError) {
-        if (err == pmHostError) {
-            midi->dictionary->host_error(midi, pm_hosterror_text, 
-                                         PM_HOST_ERROR_MSG_LEN);
-          pm_hosterror = TRUE;
-        }
-        return pm_errmsg(err);
-    }
-
-    while (n < length) {
-        PmError err = Pm_Dequeue(midi->queue, buffer++);
-        if (err == pmBufferOverflow) {
-            /* ignore the data we have retreived so far */
-            return pm_errmsg(pmBufferOverflow);
-        } else if (err == 0) { /* empty queue */
-            break;
-        }
-        n++;
-    }
-    return n;
-}
-
-PmError Pm_Poll( PortMidiStream *stream )
-{
-    PmInternal *midi = (PmInternal *) stream;
-    PmError err;
-
-    pm_hosterror = FALSE;
-    /* arg checking */
-    if(midi == NULL)
-        err = pmBadPtr;
-    else if (!descriptors[midi->device_id].pub.opened)
-        err = pmBadPtr;
-    else if (!descriptors[midi->device_id].pub.input)
-        err = pmBadPtr;
-    else
-        err = (*(midi->dictionary->poll))(midi);
-
-    if (err != pmNoError) {
-        if (err == pmHostError) {
-            midi->dictionary->host_error(midi, pm_hosterror_text, 
-                                         PM_HOST_ERROR_MSG_LEN);
-           pm_hosterror = TRUE;
-        }
-        return pm_errmsg(err);
-    }
-
-    return !Pm_QueueEmpty(midi->queue);
-}
-
-
-/* this is called from Pm_Write and Pm_WriteSysEx to issue a
- * call to the system-dependent end_sysex function and handle 
- * the error return
- */
-static PmError pm_end_sysex(PmInternal *midi)
-{
-    PmError err = (*midi->dictionary->end_sysex)(midi, 0);
-    midi->sysex_in_progress = FALSE;
-    if (err == pmHostError) {
-        midi->dictionary->host_error(midi, pm_hosterror_text, 
-                                     PM_HOST_ERROR_MSG_LEN);
-        pm_hosterror = TRUE;
-    }
-    return err;
-}
-
-
-/* to facilitate correct error-handling, Pm_Write, Pm_WriteShort, and
-   Pm_WriteSysEx all operate a state machine that "outputs" calls to
-   write_short, begin_sysex, write_byte, end_sysex, and write_realtime */
-
-PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length)
-{
-    PmInternal *midi = (PmInternal *) stream;
-    PmError err = pmNoError;
-    int i;
-    int bits;
-    
-    pm_hosterror = FALSE;
-    /* arg checking */
-    if(midi == NULL)
-        err = pmBadPtr;
-    else if(!descriptors[midi->device_id].pub.opened)
-        err = pmBadPtr;
-    else if(!descriptors[midi->device_id].pub.output)
-        err = pmBadPtr;
-    else
-        err = pmNoError;
-    
-    if (err != pmNoError) goto pm_write_error;
-    
-    if (midi->latency == 0) {
-        midi->now = 0;
-    } else {
-        midi->now = (*(midi->time_proc))(midi->time_info);
-        if (midi->first_message || midi->sync_time + 100 /*ms*/ < midi->now) {
-            /* time to resync */
-            midi->now = (*midi->dictionary->synchronize)(midi);
-            midi->first_message = FALSE;
-        }
-    }
-    /* error recovery: when a sysex is detected, we call
-     *   dictionary->begin_sysex() followed by calls to
-     *   dictionary->write_byte() and dictionary->write_realtime()
-     *   until an end-of-sysex is detected, when we call
-     *   dictionary->end_sysex(). After an error occurs, 
-     *   Pm_Write() continues to call functions. For example,
-     *   it will continue to call write_byte() even after
-     *   an error sending a sysex message, and end_sysex() will be
-     *   called when an EOX or non-real-time status is found.
-     * When errors are detected, Pm_Write() returns immediately, 
-     *   so it is possible that this will drop data and leave
-     *   sysex messages in a partially transmitted state.
-     */
-    for (i = 0; i < length; i++) {
-        unsigned long msg = buffer[i].message;
-        bits = 0;
-        /* is this a sysex message? */
-        if (Pm_MessageStatus(msg) == MIDI_SYSEX) {
-            if (midi->sysex_in_progress) {
-                /* error: previous sysex was not terminated by EOX */
-                midi->sysex_in_progress = FALSE;
-                err = pmBadData;
-                goto pm_write_error;
-            }
-            midi->sysex_in_progress = TRUE;
-            if ((err = (*midi->dictionary->begin_sysex)(midi, 
-                               buffer[i].timestamp)) != pmNoError)
-                goto pm_write_error;
-            if ((err = (*midi->dictionary->write_byte)(midi, MIDI_SYSEX,
-                               buffer[i].timestamp)) != pmNoError) 
-                goto pm_write_error;
-            bits = 8;
-            /* fall through to continue sysex processing */
-        } else if ((msg & MIDI_STATUS_MASK) && 
-                   (Pm_MessageStatus(msg) != MIDI_EOX)) {
-            /* a non-sysex message */
-            if (midi->sysex_in_progress) {
-                /* this should be a realtime message */
-                if (is_real_time(msg)) {
-                    if ((err = (*midi->dictionary->write_realtime)(midi, 
-                                       &(buffer[i]))) != pmNoError)
-                        goto pm_write_error;
-                } else {
-                    midi->sysex_in_progress = FALSE;
-                    err = pmBadData;
-                    /* ignore any error from this, because we already have one */
-                    /* pass 0 as timestamp -- it's ignored */
-                    (*midi->dictionary->end_sysex)(midi, 0);
-                    goto pm_write_error;
-                }
-            } else { /* regular short midi message */
-                if ((err = (*midi->dictionary->write_short)(midi, 
-                                   &(buffer[i]))) != pmNoError)
-                    goto pm_write_error;
-                continue;
-            }
-        }
-        if (midi->sysex_in_progress) { /* send sysex bytes until EOX */
-            /* see if we can accelerate data transfer */
-            if (bits == 0 && midi->fill_base && /* 4 bytes to copy */
-                (*midi->fill_offset_ptr) + 4 <= midi->fill_length &&
-                (msg & 0x80808080) == 0) { /* all data */
-                    /* copy 4 bytes from msg to fill_base + fill_offset */
-                    unsigned char *ptr = midi->fill_base + 
-                                         *(midi->fill_offset_ptr);
-                    ptr[0] = msg; ptr[1] = msg >> 8; 
-                    ptr[2] = msg >> 18; ptr[3] = msg >> 24;
-                    (*midi->fill_offset_ptr) += 4;
-                     continue;
-            }
-            /* no acceleration, so do byte-by-byte copying */
-            while (bits < 32) {
-                unsigned char midi_byte = (unsigned char) (msg >> bits);
-                if ((err = (*midi->dictionary->write_byte)(midi, midi_byte, 
-                                   buffer[i].timestamp)) != pmNoError)
-                    goto pm_write_error;
-                if (midi_byte == MIDI_EOX) {
-                    err = pm_end_sysex(midi);
-                    if (err != pmNoError) goto error_exit;
-                    break; /* from while loop */
-                }
-                bits += 8;
-            }
-        } else {
-            /* not in sysex mode, but message did not start with status */
-            err = pmBadData;
-            goto pm_write_error;
-        }
-    }
-    /* after all messages are processed, send the data */
-    if (!midi->sysex_in_progress)
-        err = (*midi->dictionary->write_flush)(midi, 0);
-pm_write_error:
-    if (err == pmHostError) {
-        midi->dictionary->host_error(midi, pm_hosterror_text, 
-                                     PM_HOST_ERROR_MSG_LEN);
-        pm_hosterror = TRUE;
-    }
-error_exit:
-    return pm_errmsg(err);
-}
-
-
-PmError Pm_WriteShort(PortMidiStream *stream, long when, long msg)
-{
-    PmEvent event;
-    
-    event.timestamp = when;
-    event.message = msg;
-    return Pm_Write(stream, &event, 1);
-}
-
-
-PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when, 
-                      unsigned char *msg)
-{
-    /* allocate buffer space for PM_DEFAULT_SYSEX_BUFFER_SIZE bytes */
-    /* each PmEvent holds sizeof(PmMessage) bytes of sysex data */
-    #define BUFLEN (PM_DEFAULT_SYSEX_BUFFER_SIZE / sizeof(PmMessage))
-    PmEvent buffer[BUFLEN];
-    int buffer_size = 1; /* first time, send 1. After that, it's BUFLEN */
-    PmInternal *midi = (PmInternal *) stream;
-    /* the next byte in the buffer is represented by an index, bufx, and
-       a shift in bits */
-    int shift = 0;
-    int bufx = 0;
-    buffer[0].message = 0;
-    buffer[0].timestamp = when;
-
-    while (1) {
-        /* insert next byte into buffer */
-        buffer[bufx].message |= ((*msg) << shift);
-        shift += 8;
-        if (*msg++ == MIDI_EOX) break;
-        if (shift == 32) {
-            shift = 0;
-            bufx++;
-            if (bufx == buffer_size) {
-                PmError err = Pm_Write(stream, buffer, buffer_size);
-                /* note: Pm_Write has already called errmsg() */
-                if (err) return err;
-                /* prepare to fill another buffer */
-                bufx = 0;
-                buffer_size = BUFLEN;
-                /* optimization: maybe we can just copy bytes */
-                if (midi->fill_base) {
-                    PmError err;
-                    while (*(midi->fill_offset_ptr) < midi->fill_length) {
-                        midi->fill_base[(*midi->fill_offset_ptr)++] = *msg;
-                        if (*msg++ == MIDI_EOX) {
-                            err = pm_end_sysex(midi);
-                            if (err != pmNoError) return pm_errmsg(err);
-                            goto end_of_sysex;
-                        }
-                    }
-                    /* I thought that I could do a pm_Write here and
-                     * change this if to a loop, avoiding calls in Pm_Write
-                     * to the slower write_byte, but since 
-                     * sysex_in_progress is true, this will not flush
-                     * the buffer, and we'll infinite loop: */
-                    /* err = Pm_Write(stream, buffer, 0);
-                       if (err) return err; */
-                    /* instead, the way this works is that Pm_Write calls
-                     * write_byte on 4 bytes. The first, since the buffer
-                     * is full, will flush the buffer and allocate a new
-                     * one. This primes the buffer so
-                     * that we can return to the loop above and fill it
-                     * efficiently without a lot of function calls.
-                     */
-                    buffer_size = 1; /* get another message started */
-                }
-            }
-            buffer[bufx].message = 0;
-            buffer[bufx].timestamp = when;
-        } 
-        /* keep inserting bytes until you find MIDI_EOX */
-    }
-end_of_sysex:
-    /* we're finished sending full buffers, but there may
-     * be a partial one left.
-     */
-    if (shift != 0) bufx++; /* add partial message to buffer len */
-    if (bufx) { /* bufx is number of PmEvents to send from buffer */
-        PmError err = Pm_Write(stream, buffer, bufx);
-        if (err) return err;
-    }
-    return pmNoError;
-}
-
-
-
-PmError Pm_OpenInput(PortMidiStream** stream,
-                     PmDeviceID inputDevice,
-                     void *inputDriverInfo,
-                     long bufferSize,
-                     PmTimeProcPtr time_proc,
-                     void *time_info)
-{
-    PmInternal *midi;
-    PmError err = pmNoError;
-    pm_hosterror = FALSE;
-    *stream = NULL;
-    
-    /* arg checking */
-    if (inputDevice < 0 || inputDevice >= pm_descriptor_index) 
-        err = pmInvalidDeviceId;
-    else if (!descriptors[inputDevice].pub.input) 
-        err =  pmBadPtr;
-    else if(descriptors[inputDevice].pub.opened)
-        err =  pmBadPtr;
-    
-    if (err != pmNoError) 
-        goto error_return;
-
-    /* create portMidi internal data */
-    midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); 
-    *stream = midi;
-    if (!midi) {
-        err = pmInsufficientMemory;
-        goto error_return;
-    }
-    midi->device_id = inputDevice;
-    midi->write_flag = FALSE;
-    midi->time_proc = time_proc;
-    midi->time_info = time_info;
-    /* windows adds timestamps in the driver and these are more accurate than
-       using a time_proc, so do not automatically provide a time proc. Non-win
-       implementations may want to provide a default time_proc in their
-       system-specific midi_out_open() method.
-     */
-    if (bufferSize <= 0) bufferSize = 256; /* default buffer size */
-    midi->queue = Pm_QueueCreate(bufferSize, sizeof(PmEvent));
-    if (!midi->queue) {
-        /* free portMidi data */
-        *stream = NULL;
-        pm_free(midi); 
-        err = pmInsufficientMemory;
-        goto error_return;
-    }
-    midi->buffer_len = bufferSize; /* portMidi input storage */
-    midi->latency = 0; /* not used */
-    midi->sysex_in_progress = FALSE;
-    midi->sysex_message = 0; 
-    midi->sysex_message_count = 0; 
-    midi->filters = PM_FILT_ACTIVE;
-    midi->channel_mask = 0xFFFF;
-    midi->sync_time = 0;
-    midi->first_message = TRUE;
-    midi->dictionary = descriptors[inputDevice].dictionary;
-    midi->fill_base = NULL;
-    midi->fill_offset_ptr = NULL;
-    midi->fill_length = 0;
-    descriptors[inputDevice].internalDescriptor = midi;
-    /* open system dependent input device */
-    err = (*midi->dictionary->open)(midi, inputDriverInfo);
-    if (err) {
-        *stream = NULL;
-        descriptors[inputDevice].internalDescriptor = NULL;
-        /* free portMidi data */
-        Pm_QueueDestroy(midi->queue);
-        pm_free(midi);
-    } else {
-        /* portMidi input open successful */
-        descriptors[inputDevice].pub.opened = TRUE;
-    }
-error_return:
-    /* note: if there is a pmHostError, it is the responsibility
-     * of the system-dependent code (*midi->dictionary->open)()
-     * to set pm_hosterror and pm_hosterror_text
-     */
-    return pm_errmsg(err);
-}
-
-
-PmError Pm_OpenOutput(PortMidiStream** stream,
-                      PmDeviceID outputDevice,
-                      void *outputDriverInfo,
-                      long bufferSize,
-                      PmTimeProcPtr time_proc,
-                      void *time_info,
-                      long latency ) 
-{
-    PmInternal *midi;
-    PmError err = pmNoError;
-    pm_hosterror = FALSE;
-    *stream =  NULL;
-    
-    /* arg checking */
-    if (outputDevice < 0 || outputDevice >= pm_descriptor_index)
-        err = pmInvalidDeviceId;
-    else if (!descriptors[outputDevice].pub.output) 
-        err = pmInvalidDeviceId;
-    else if (descriptors[outputDevice].pub.opened)
-        err = pmInvalidDeviceId;
-    if (err != pmNoError) 
-        goto error_return;
-
-    /* create portMidi internal data */
-    midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); 
-    *stream = midi;                 
-    if (!midi) {
-        err = pmInsufficientMemory;
-        goto error_return;
-    }
-    midi->device_id = outputDevice;
-    midi->write_flag = TRUE;
-    midi->time_proc = time_proc;
-    /* if latency > 0, we need a time reference. If none is provided,
-       use PortTime library */
-    if (time_proc == NULL && latency != 0) {
-        if (!Pt_Started()) 
-            Pt_Start(1, 0, 0);
-        /* time_get does not take a parameter, so coerce */
-        midi->time_proc = (PmTimeProcPtr) Pt_Time;
-    }
-    midi->time_info = time_info;
-    midi->buffer_len = bufferSize;
-    midi->queue = NULL; /* unused by output */
-    /* if latency zero, output immediate (timestamps ignored) */
-    /* if latency < 0, use 0 but don't return an error */
-    if (latency < 0) latency = 0;
-    midi->latency = latency;
-    midi->sysex_in_progress = FALSE;
-    midi->sysex_message = 0; /* unused by output */
-    midi->sysex_message_count = 0; /* unused by output */
-    midi->filters = 0; /* not used for output */
-    midi->channel_mask = 0xFFFF;
-    midi->sync_time = 0;
-    midi->first_message = TRUE;
-    midi->dictionary = descriptors[outputDevice].dictionary;
-    midi->fill_base = NULL;
-    midi->fill_offset_ptr = NULL;
-    midi->fill_length = 0;
-    descriptors[outputDevice].internalDescriptor = midi;
-    /* open system dependent output device */
-    err = (*midi->dictionary->open)(midi, outputDriverInfo);
-    if (err) {
-        *stream = NULL;
-        descriptors[outputDevice].internalDescriptor = NULL;
-        /* free portMidi data */
-        pm_free(midi); 
-    } else {
-        /* portMidi input open successful */
-        descriptors[outputDevice].pub.opened = TRUE;
-    }
-error_return:
-    /* note: system-dependent code must set pm_hosterror and
-     * pm_hosterror_text if a pmHostError occurs
-     */
-    return pm_errmsg(err);
-}
-
-
-PmError Pm_SetChannelMask(PortMidiStream *stream, int mask)
-{
-    PmInternal *midi = (PmInternal *) stream;
-    PmError err = pmNoError;
-
-    if (midi == NULL)
-        err = pmBadPtr;
-    else
-        midi->channel_mask = mask;
-
-    return pm_errmsg(err);
-}
-
-
-PmError Pm_SetFilter(PortMidiStream *stream, long filters) {
-    PmInternal *midi = (PmInternal *) stream;
-    PmError err = pmNoError;
-
-    /* arg checking */
-    if (midi == NULL)
-        err = pmBadPtr;
-    else if (!descriptors[midi->device_id].pub.opened)
-        err = pmBadPtr;
-    else
-        midi->filters = filters;
-    return pm_errmsg(err);
-}
-
-
-PmError Pm_Close( PortMidiStream *stream ) {
-    PmInternal *midi = (PmInternal *) stream;
-    PmError err = pmNoError;
-
-    pm_hosterror = FALSE;
-    /* arg checking */
-    if (midi == NULL) /* midi must point to something */
-        err = pmBadPtr;
-    /* if it is an open device, the device_id will be valid */
-    else if (midi->device_id < 0 || midi->device_id >= pm_descriptor_index)
-        err = pmBadPtr;
-    /* and the device should be in the opened state */
-    else if (!descriptors[midi->device_id].pub.opened)
-        err = pmBadPtr;
-    
-    if (err != pmNoError) 
-        goto error_return;
-
-    /* close the device */
-    err = (*midi->dictionary->close)(midi);
-    /* even if an error occurred, continue with cleanup */
-    descriptors[midi->device_id].internalDescriptor = NULL;
-    descriptors[midi->device_id].pub.opened = FALSE;
-    if (midi->queue) Pm_QueueDestroy(midi->queue);
-    pm_free(midi); 
-error_return:
-    /* system dependent code must set pm_hosterror and
-     * pm_hosterror_text if a pmHostError occurs.
-     */
-    return pm_errmsg(err);
-}
-
-
-PmError Pm_Abort( PortMidiStream* stream ) {
-    PmInternal *midi = (PmInternal *) stream;
-    PmError err;
-    /* arg checking */
-    if (midi == NULL)
-        err = pmBadPtr;
-    if (!descriptors[midi->device_id].pub.output)
-        err = pmBadPtr;
-    if (!descriptors[midi->device_id].pub.opened)
-        err = pmBadPtr;
-    else
-        err = (*midi->dictionary->abort)(midi);
-
-    if (err == pmHostError) {
-        midi->dictionary->host_error(midi, pm_hosterror_text, 
-                                     PM_HOST_ERROR_MSG_LEN);
-        pm_hosterror = TRUE;
-    }
-    return pm_errmsg(err);
-}
-
-
-
-/* pm_channel_filtered returns non-zero if the channel mask is blocking the current channel */
-#define pm_channel_filtered(status, mask) \
-    ((((status) & 0xF0) != 0xF0) && (!(Pm_Channel((status) & 0x0F) & (mask))))
-
-
-/* The following two functions will checks to see if a MIDI message matches
-   the filtering criteria.  Since the sysex routines only want to filter realtime messages,
-   we need to have separate routines.
- */
-
-
-/* pm_realtime_filtered returns non-zero if the filter will kill the current message.
-   Note that only realtime messages are checked here.
- */
-#define pm_realtime_filtered(status, filters) \
-    ((((status) & 0xF0) == 0xF0) && ((1 << ((status) & 0xF)) & (filters)))
-
-/*
-    return ((status == MIDI_ACTIVE) && (filters & PM_FILT_ACTIVE))
-            ||  ((status == MIDI_CLOCK) && (filters & PM_FILT_CLOCK))
-            ||  ((status == MIDI_START) && (filters & PM_FILT_PLAY))
-            ||  ((status == MIDI_STOP) && (filters & PM_FILT_PLAY))
-            ||  ((status == MIDI_CONTINUE) && (filters & PM_FILT_PLAY))
-            ||  ((status == MIDI_F9) && (filters & PM_FILT_F9))
-            ||  ((status == MIDI_FD) && (filters & PM_FILT_FD))
-            ||  ((status == MIDI_RESET) && (filters & PM_FILT_RESET))
-            ||  ((status == MIDI_MTC) && (filters & PM_FILT_MTC))
-            ||  ((status == MIDI_SONGPOS) && (filters & PM_FILT_SONG_POSITION))
-            ||  ((status == MIDI_SONGSEL) && (filters & PM_FILT_SONG_SELECT))
-            ||  ((status == MIDI_TUNE) && (filters & PM_FILT_TUNE));
-}*/
-
-
-/* pm_status_filtered returns non-zero if a filter will kill the current message, based on status.
-   Note that sysex and real time are not checked.  It is up to the subsystem (winmm, core midi, alsa)
-   to filter sysex, as it is handled more easily and efficiently at that level.
-   Realtime message are filtered in pm_realtime_filtered.
- */
-#define pm_status_filtered(status, filters) ((1 << (16 + ((status) >> 4))) & (filters))
-
-
-/*
-    return  ((status == MIDI_NOTE_ON) && (filters & PM_FILT_NOTE))
-            ||  ((status == MIDI_NOTE_OFF) && (filters & PM_FILT_NOTE))
-            ||  ((status == MIDI_CHANNEL_AT) && (filters & PM_FILT_CHANNEL_AFTERTOUCH))
-            ||  ((status == MIDI_POLY_AT) && (filters & PM_FILT_POLY_AFTERTOUCH))
-            ||  ((status == MIDI_PROGRAM) && (filters & PM_FILT_PROGRAM))
-            ||  ((status == MIDI_CONTROL) && (filters & PM_FILT_CONTROL))
-            ||  ((status == MIDI_PITCHBEND) && (filters & PM_FILT_PITCHBEND));
-
-}
-*/
-
-static void pm_flush_sysex(PmInternal *midi, PmTimestamp timestamp)
-{
-    PmEvent event;
-    
-    /* there may be nothing in the buffer */
-    if (midi->sysex_message_count == 0) return; /* nothing to flush */
-    
-    event.message = midi->sysex_message;
-    event.timestamp = timestamp;
-    /* copied from pm_read_short, avoids filtering */
-    if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) {
-        midi->sysex_in_progress = FALSE;
-    }
-    midi->sysex_message_count = 0;
-    midi->sysex_message = 0;
-}
-
-
-/* pm_read_short and pm_read_bytes
-   are the interface between system-dependent MIDI input handlers
-   and the system-independent PortMIDI code.
-   The input handler MUST obey these rules:
-   1) all short input messages must be sent to pm_read_short, which
-      enqueues them to a FIFO for the application.
-   2) eash buffer of sysex bytes should be reported by calling pm_read_bytes
-      (which sets midi->sysex_in_progress). After the eox byte, 
-      pm_read_bytes will clear sysex_in_progress
- */
-
-/* pm_read_short is the place where all input messages arrive from 
-   system-dependent code such as pmwinmm.c. Here, the messages
-   are entered into the PortMidi input buffer. 
- */
-void pm_read_short(PmInternal *midi, PmEvent *event)
-{ 
-    int status;
-    /* arg checking */
-    assert(midi != NULL);
-    /* midi filtering is applied here */
-    status = Pm_MessageStatus(event->message);
-    if (!pm_status_filtered(status, midi->filters)
-        && (!is_real_time(status) || 
-            !pm_realtime_filtered(status, midi->filters))
-        && !pm_channel_filtered(status, midi->channel_mask)) {
-        /* if sysex is in progress and we get a status byte, it had
-           better be a realtime message or the starting SYSEX byte;
-           otherwise, we exit the sysex_in_progress state
-         */
-        if (midi->sysex_in_progress && (status & MIDI_STATUS_MASK)) {
-            /* two choices: real-time or not. If it's real-time, then
-             * this should be delivered as a sysex byte because it is
-             * embedded in a sysex message
-             */
-            if (is_real_time(status)) {
-                midi->sysex_message |= 
-                        (status << (8 * midi->sysex_message_count++));
-                if (midi->sysex_message_count == 4) {
-                    pm_flush_sysex(midi, event->timestamp);
-                }
-            } else { /* otherwise, it's not real-time. This interrupts
-                      * a sysex message in progress */
-                midi->sysex_in_progress = FALSE;
-            }
-        } else if (Pm_Enqueue(midi->queue, event) == pmBufferOverflow) {
-            midi->sysex_in_progress = FALSE;
-        }
-    }
-}
-
-/* pm_read_bytes -- read one (partial) sysex msg from MIDI data */
-/*
- * returns how many bytes processed
- */
-unsigned int pm_read_bytes(PmInternal *midi, unsigned char *data, 
-                    int len, PmTimestamp timestamp)
-{
-    unsigned int i = 0; /* index into data */
-    PmEvent event;
-    event.timestamp = timestamp;
-    assert(midi);
-    /* note that since buffers may not have multiples of 4 bytes,
-     * pm_read_bytes may be called in the middle of an outgoing
-     * 4-byte PortMidi message. sysex_in_progress indicates that
-     * a sysex has been sent but no eox.
-     */
-    if (len == 0) return 0; /* sanity check */
-    if (!midi->sysex_in_progress) {
-        while (i < len) { /* process all data */
-            unsigned char byte = data[i++];
-            if (byte == MIDI_SYSEX &&
-                !pm_realtime_filtered(byte, midi->filters)) {
-                midi->sysex_in_progress = TRUE;
-                i--; /* back up so code below will get SYSEX byte */
-                break; /* continue looping below to process msg */
-            } else if (byte == MIDI_EOX) {
-                midi->sysex_in_progress = FALSE;
-                return i; /* done with one message */
-            } else if (byte & MIDI_STATUS_MASK) {
-                /* We're getting MIDI but no sysex in progress.
-                 * Either the SYSEX status byte was dropped or
-                 * the message was filtered. Drop the data, but
-                 * send any embedded realtime bytes.
-                 */
-                /* assume that this is a real-time message:
-                 * it is an error to pass non-real-time messages
-                 * to pm_read_bytes
-                 */
-                event.message = byte;
-                pm_read_short(midi, &event);
-            }
-        } /* all bytes in the buffer are processed */
-    }
-    /* Now, i<len implies sysex_in_progress. If sysex_in_progress
-     * becomes false in the loop, there must have been an overflow
-     * and we can just drop all remaining bytes 
-     */
-    while (i < len && midi->sysex_in_progress) {
-        if (midi->sysex_message_count == 0 && i <= len - 4 &&
-            ((event.message = (((long) data[i]) | 
-                             (((long) data[i+1]) << 8) |
-                             (((long) data[i+2]) << 16) |
-                             (((long) data[i+3]) << 24))) &
-             0x80808080) == 0) { /* all data, no status */ 
-            if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) {
-                midi->sysex_in_progress = FALSE;
-            }
-            i += 4;
-        } else {
-            while (i < len) {
-                /* send one byte at a time */
-                unsigned char byte = data[i++];
-                if (is_real_time(byte) && 
-                    pm_realtime_filtered(byte, midi->filters)) {
-                    continue; /* real-time data is filtered, so omit */
-                }
-                midi->sysex_message |= 
-                    (byte << (8 * midi->sysex_message_count++));
-                if (byte == MIDI_EOX) {
-                    midi->sysex_in_progress = FALSE;
-                    pm_flush_sysex(midi, event.timestamp);
-                    return i;
-                } else if (midi->sysex_message_count == 4) {
-                    pm_flush_sysex(midi, event.timestamp);
-                    /* after handling at least one non-data byte
-                     * and reaching a 4-byte message boundary,
-                     * resume trying to send 4 at a time in outer loop
-                     */
-                    break;
-                }
-            }
-        }
-    }
-    return i;
-}
-
-
+/* portmidi.c -- cross-platform MIDI I/O library */+/* see license.txt for license */++#include "stdlib.h"+#include "string.h"+#include "portmidi.h"+#include "porttime.h"+#include "pmutil.h"+#include "pminternal.h"+#include <assert.h>++#define MIDI_CLOCK      0xf8+#define MIDI_ACTIVE     0xfe+#define MIDI_STATUS_MASK 0x80+#define MIDI_SYSEX      0xf0+#define MIDI_EOX        0xf7+#define MIDI_START      0xFA+#define MIDI_STOP       0xFC+#define MIDI_CONTINUE   0xFB+#define MIDI_F9         0xF9+#define MIDI_FD         0xFD+#define MIDI_RESET      0xFF+#define MIDI_NOTE_ON    0x90+#define MIDI_NOTE_OFF   0x80+#define MIDI_CHANNEL_AT 0xD0+#define MIDI_POLY_AT    0xA0+#define MIDI_PROGRAM    0xC0+#define MIDI_CONTROL    0xB0+#define MIDI_PITCHBEND  0xE0+#define MIDI_MTC        0xF1+#define MIDI_SONGPOS    0xF2+#define MIDI_SONGSEL    0xF3+#define MIDI_TUNE       0xF6++#define is_empty(midi) ((midi)->tail == (midi)->head)++/* these are not static so that (possibly) some system-dependent code+ * could override the portmidi.c default which is to use the first+ * device added using pm_add_device()+ */+PmDeviceID pm_default_input_device_id = -1;+PmDeviceID pm_default_output_device_id = -1;++/* this is not static so that pm_init can set it directly+ *   (see pmmac.c:pm_init())+ */+int pm_initialized = FALSE;++int pm_hosterror;  /* boolean */++/* if PM_CHECK_ERRORS is enabled, but the caller wants to+ * handle an error condition, declare this as extern and + * set to FALSE (this override is provided specifically+ * for the test program virttest.c, where pmNameConflict + * is expected in a call to Pm_CreateVirtualInput()):+ */+int pm_check_errors = TRUE;++char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN];++#ifdef PM_CHECK_ERRORS++#include <stdio.h>++#define STRING_MAX 80++static void prompt_and_exit(void)+{+    char line[STRING_MAX];+    printf("type ENTER...");+    char *rslt = fgets(line, STRING_MAX, stdin);+    /* this will clean up open ports: */+    exit(-1);+}++static PmError pm_errmsg(PmError err)+{+    if (!pm_check_errors) { /* see pm_check_errors declaration above */+        ;+    } else if (err == pmHostError) {+        /* it seems pointless to allocate memory and copy the string,+         * so I will do the work of Pm_GetHostErrorText directly+         */+        printf("PortMidi found host error...\n  %s\n", pm_hosterror_text);+        pm_hosterror = FALSE;+        pm_hosterror_text[0] = 0; /* clear the message */+        prompt_and_exit();+    } else if (err < 0) {+        printf("PortMidi call failed...\n  %s\n", Pm_GetErrorText(err));+        prompt_and_exit();+    }+    return err;+}+#else+#define pm_errmsg(err) err+#endif+++int pm_midi_length(PmMessage msg)+{+    int status, high, low;+    static int high_lengths[] = {+        1, 1, 1, 1, 1, 1, 1, 1,         /* 0x00 through 0x70 */+        3, 3, 3, 3, 2, 2, 3, 1          /* 0x80 through 0xf0 */+    };+    static int low_lengths[] = {+        1, 2, 3, 2, 1, 1, 1, 1,         /* 0xf0 through 0xf8 */+        1, 1, 1, 1, 1, 1, 1, 1          /* 0xf9 through 0xff */+    };++    status = msg & 0xFF;+    high = status >> 4;+    low = status & 15;++    return (high != 0xF) ? high_lengths[high] : low_lengths[low];+}+++/*+====================================================================+system implementation of portmidi interface+====================================================================+*/++int pm_descriptor_max = 0;+int pm_descriptor_len = 0;+descriptor_type pm_descriptors = NULL;++/* interface pm_descriptors are simple: an array of string/fnptr pairs: */+#define MAX_INTERF 4+static struct {+    const char *interf;+    pm_create_fn create_fn;+    pm_delete_fn delete_fn;+} pm_interf_list[MAX_INTERF];++static int pm_interf_list_len = 0;+++/* pm_add_interf -- describe an interface to library+ *+ * This is called at initialization time, once for each+ * supported interface (e.g., CoreMIDI). The strings+ * are retained but NOT COPIED, so do not destroy them!+ *+ * The purpose is to register functions that create/delete+ * a virtual input or output device.+ *+ * returns pmInsufficientMemor if interface memory is+ * exceeded, otherwise returns pmNoError.+ */+PmError pm_add_interf(const char *interf, pm_create_fn create_fn,+                      pm_delete_fn delete_fn)+{+    if (pm_interf_list_len >= MAX_INTERF) {+        return pmInsufficientMemory;+    }+    pm_interf_list[pm_interf_list_len].interf = interf;+    pm_interf_list[pm_interf_list_len].create_fn = create_fn;+    pm_interf_list[pm_interf_list_len].delete_fn = delete_fn;+    pm_interf_list_len++;+    return pmNoError;+}+++PmError pm_create_virtual(PmInternal *midi, int is_input, const char *interf,+                          const char *name, void *device_info)+{+    int i;+    if (pm_interf_list_len == 0) {+        return pmNotImplemented;+    }+    if (!interf) {+        /* default interface is the first one */+        interf = pm_interf_list[0].interf;+    }+    for (i = 0; i < pm_interf_list_len; i++) {+        if (strcmp(pm_interf_list[i].interf,+                   interf) == 0) {+            int id = (*pm_interf_list[i].create_fn)(is_input, name,+                                                        device_info);+            pm_descriptors[id].pub.is_virtual = TRUE;+            return id;+        }+    }+    return pmInterfaceNotSupported;+}+++/* pm_add_device -- describe interface/device pair to library + *+ * This is called at intialization time, once for each + * interface (e.g. DirectSound) and device (e.g. SoundBlaster 1).+ * This is also called when user creates a virtual device.+ * + * Normally, increasing integer indices are returned. If the device+ * is virtual, a linear search is performed to ensure that the name+ * is unique. If the name is already taken, the call will fail and+ * no device is added.+ *+ * interf is assumed to be static memory, so it is NOT COPIED and + * NOT FREED.+ * name is owned by caller, COPIED if needed, and FREED by PortMidi.+ * Caller is resposible for freeing name when pm_add_device returns.+ *+ * returns pmInvalidDeviceId if device memory is exceeded or a virtual+ * device would take the name of an existing device.+ * otherwise returns index (portmidi device_id) of the added device+ */+PmError pm_add_device(const char *interf, const char *name, int is_input, +                int is_virtual, void *descriptor, pm_fns_type dictionary) {+    /* printf("pm_add_device: %s %s %d %p %p\n",+           interf, name, is_input, descriptor, dictionary); */+    int device_id;+    PmDeviceInfo *d;+    /* if virtual, search for duplicate name or unused ID; otherwise,+     * just add a new device at the next integer available:+     */+    for (device_id = (is_virtual ? 0 : pm_descriptor_len);+         device_id < pm_descriptor_len; device_id++) {+        d = &pm_descriptors[device_id].pub;+        d->structVersion = PM_DEVICEINFO_VERS;+        if (strcmp(d->interf, interf) == 0 && strcmp(d->name, name) == 0) {+            /* only reuse a name if it is a deleted virtual device with+             * a matching direction (input or output) */+            if (pm_descriptors[device_id].deleted && is_input == d->input) {+                /* here, we know d->is_virtual because only virtual devices+                 * can be deleted, and we know is_virtual because we are+                 * in this loop. +                 */+                pm_free((void *) d->name);  /* reuse this device entry */+                d->name = NULL;+                break;+            /* name conflict exists if the new device appears to others as+             * the same direction (input or output) as the existing device.+             * Note that virtual inputs appear to others as outputs and+             * vice versa.+             * The direction of the new virtual device to others is "output" +             * if is_input, i.e., virtual inputs appear to others as outputs. +             * The existing device appears to others as "output" if+             *     (d->is_virtual == d->input) by the same logic.+             * The compare will detect if device directions are the same:+             */+            } else if (is_input == (d->is_virtual == d->input)) {+                return pmNameConflict;+            }+        }+    }+    if (device_id >= pm_descriptor_max) {+        /* expand pm_descriptors */+        descriptor_type new_descriptors = (descriptor_type) +            pm_alloc(sizeof(descriptor_node) * (pm_descriptor_max + 32));+        if (!new_descriptors) return pmInsufficientMemory;+        if (pm_descriptors) {+            memcpy(new_descriptors, pm_descriptors, +                   sizeof(descriptor_node) * pm_descriptor_max);+            pm_free(pm_descriptors);+        }+        pm_descriptor_max += 32;+        pm_descriptors = new_descriptors;+    }+    if (device_id == pm_descriptor_len) {+        pm_descriptor_len++;  /* extending array of pm_descriptors */+    }+    d = &pm_descriptors[device_id].pub;+    d->interf = interf;+    d->name = pm_alloc(strlen(name) + 1);+    if (!d->name) {+        return pmInsufficientMemory;+    }+#if defined(WIN32) && !defined(_WIN32)+#pragma warning(suppress: 4996)  /* don't use suggested strncpy_s */+#endif+    strcpy(d->name, name);+    d->input = is_input;+    d->output = !is_input;+    d->is_virtual = FALSE;  /* caller should set to TRUE if this is virtual */++    /* default state: nothing to close (for automatic device closing) */+    d->opened = FALSE;++    pm_descriptors[device_id].deleted = FALSE;++    /* ID number passed to win32 multimedia API open */+    pm_descriptors[device_id].descriptor = descriptor;+    +    /* points to PmInternal, allows automatic device closing */+    pm_descriptors[device_id].pm_internal = NULL;++    pm_descriptors[device_id].dictionary = dictionary;++    /* set the defaults to the first input and output we see */+    if (is_input && pm_default_input_device_id == -1) {+        pm_default_input_device_id = device_id;+    } else if (!is_input && pm_default_output_device_id == -1) {+        pm_default_output_device_id = device_id;+    }+    +    return device_id;+}+++/* Undo a successful call to pm_add_device(). If a new device was+ * allocated, it must be the last device in pm_descriptors, so it is+ * easy to delete by decrementing the length of pm_descriptors, but+ * first free the name (which was copied to the heap). Otherwise,+ * the device must be a virtual device that was created previously+ * and is in the interior of the array of pm_descriptors. Leave it,+ * but mark it as deleted.+ */+void pm_undo_add_device(int id)+{+    /* Clear some fields (not all are strictly necessary) */ +    pm_descriptors[id].deleted = TRUE;+    pm_descriptors[id].descriptor = NULL;+    pm_descriptors[id].pm_internal = NULL;++    if (id == pm_descriptor_len - 1) {+        pm_free(pm_descriptors[id].pub.name);+        pm_descriptor_len--;+    }+}+++/* utility to look up device, given a pattern, +   note: pattern is modified+ */+int Pm_FindDevice(char *pattern, int is_input)+{+    int id = pmNoDevice;+    int i;+    /* first parse pattern into name, interf parts */+    const char *interf_pref = ""; /* initially assume it is not there */+    char *name_pref = strstr(pattern, ", ");++    if (name_pref) { /* found separator, adjust the pointer */+        interf_pref = pattern;+        name_pref[0] = 0;+        name_pref += 2;+    } else {+        name_pref = pattern; /* whole string is the name pattern */+    }+    for (i = 0; i < pm_descriptor_len; i++) {+        const PmDeviceInfo *info = Pm_GetDeviceInfo(i);+        if (info &&+            info->input == is_input &&+            strstr(info->name, name_pref) &&+            strstr(info->interf, interf_pref)) {+            id = i;+            break;+        }+    }    +    return id;+}+++/*+====================================================================+portmidi implementation+====================================================================+*/++PMEXPORT int Pm_CountDevices(void)+{+    Pm_Initialize();+    /* no error checking -- Pm_Initialize() does not fail */+    return pm_descriptor_len;+}+++PMEXPORT const PmDeviceInfo* Pm_GetDeviceInfo(PmDeviceID id)+{+    Pm_Initialize(); /* no error check needed */+    if (id >= 0 && id < pm_descriptor_len && !pm_descriptors[id].deleted) {+        return &pm_descriptors[id].pub;+    }+    return NULL;+}++/* pm_success_fn -- "noop" function pointer */+PmError pm_success_fn(PmInternal *midi)+{+    return pmNoError;+}++/* none_write -- returns an error if called */+PmError none_write_short(PmInternal *midi, PmEvent *buffer)+{+    return pmBadPtr;+}++/* pm_fail_timestamp_fn -- placeholder for begin_sysex and flush */+PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp)+{+    return pmBadPtr;+}++PmError none_write_byte(PmInternal *midi, unsigned char byte, +                        PmTimestamp timestamp)+{+    return pmBadPtr;+}++/* pm_fail_fn -- generic function, returns error if called */+PmError pm_fail_fn(PmInternal *midi)+{+    return pmBadPtr;+}++static PmError none_open(PmInternal *midi, void *driverInfo)+{+    return pmBadPtr;+}++static unsigned int none_check_host_error(PmInternal * midi)+{+    return FALSE;+}++PmTimestamp none_synchronize(PmInternal *midi)+{+    return 0;+}++#define none_abort pm_fail_fn+#define none_close pm_fail_fn++pm_fns_node pm_none_dictionary = {+    none_write_short,+    none_sysex,+    none_sysex,+    none_write_byte,+    none_write_short,+    none_write_flush,+    none_synchronize,+    none_open,+    none_abort,+    none_close,+    none_poll,+    none_check_host_error,+};+++PMEXPORT const char *Pm_GetErrorText(PmError errnum)+{+    const char *msg;++    switch(errnum)+    {+    case pmNoError:                  +        msg = ""; +        break;+    case pmHostError:                +        msg = "PortMidi: Host error"; +        break;+    case pmInvalidDeviceId:          +        msg = "PortMidi: Invalid device ID"; +        break;+    case pmInsufficientMemory:       +        msg = "PortMidi: Insufficient memory"; +        break;+    case pmBufferTooSmall:           +        msg = "PortMidi: Buffer too small"; +        break;+    case pmBadPtr:                   +        msg = "PortMidi: Bad pointer";+        break;+    case pmInternalError:            +        msg = "PortMidi: Internal PortMidi Error";+        break;+    case pmBufferOverflow:+        msg = "PortMidi: Buffer overflow";+        break;+    case pmBadData:+        msg = "PortMidi: Invalid MIDI message Data";+        break;+    case pmBufferMaxSize:+        msg = "PortMidi: Buffer cannot be made larger";+        break;+    case pmNotImplemented:+        msg = "PortMidi: Function is not implemented";+        break;+    case pmInterfaceNotSupported:+        msg = "PortMidi: Interface not supported";+        break;+    case pmNameConflict:+        msg = "PortMidi: Cannot create virtual device: name is taken";+        break;+    case pmDeviceRemoved:+        msg = "PortMidi: Output attempted after (USB) device removed";+        break;+    default:+        msg = "PortMidi: Illegal error number";+        break;+    }+    return msg;+}+++/* This can be called whenever you get a pmHostError return value+ * or TRUE from Pm_HasHostError().+ * The error will always be in the global pm_hosterror_text.+ */+PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len)+{+    assert(msg);+    assert(len > 0);+    if (pm_hosterror) {+#if defined(WIN32) && !defined(_WIN32)+#pragma warning(suppress: 4996)  /* don't use suggested strncpy_s */+#endif+        strncpy(msg, (char *) pm_hosterror_text, len);+        pm_hosterror = FALSE;+        pm_hosterror_text[0] = 0; /* clear the message; not necessary, but it+                                     might help with debugging */+        msg[len - 1] = 0; /* make sure string is terminated */+    } else {+        msg[0] = 0; /* no string to return */+    }+}+++PMEXPORT int Pm_HasHostError(PortMidiStream * stream)+{+    if (pm_hosterror) return TRUE;+    if (stream) {+        PmInternal * midi = (PmInternal *) stream;+        return (*midi->dictionary->check_host_error)(midi);+    }+    return FALSE;+}+++PMEXPORT PmError Pm_Initialize(void)+{+    if (!pm_initialized) {+        pm_descriptor_len = 0;+        pm_interf_list_len = 0;+        pm_hosterror = FALSE;+        pm_hosterror_text[0] = 0; /* the null string */+        pm_init();+        pm_initialized = TRUE;+    }+    return pmNoError;+}+++PMEXPORT PmError Pm_Terminate(void)+{+    if (pm_initialized) {+        pm_term();+        /* if there are no devices, pm_descriptors might still be NULL */+        if (pm_descriptors != NULL) {+            int i;  /* free names copied into pm_descriptors */+            for (i = 0; i < pm_descriptor_len; i++) {+                if (pm_descriptors[i].pub.name) {+                    pm_free(pm_descriptors[i].pub.name);+                }+            }+            pm_free(pm_descriptors);+            pm_descriptors = NULL;+        }+        pm_descriptor_len = 0;+        pm_descriptor_max = 0;+        pm_interf_list_len = 0;+        pm_initialized = FALSE;+    }+    return pmNoError;+}+++/* Pm_Read -- read up to length messages from source into buffer */+/*+ * returns number of messages actually read, or error code+ */+PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length)+{+    PmInternal *midi = (PmInternal *) stream;+    int n = 0;+    PmError err = pmNoError;+    pm_hosterror = FALSE;+    /* arg checking */+    if(midi == NULL)+        err = pmBadPtr;+    else if(!pm_descriptors[midi->device_id].pub.opened)+        err = pmBadPtr;+    else if(!pm_descriptors[midi->device_id].pub.input)+        err = pmBadPtr;    +    /* First poll for data in the buffer...+     * This either simply checks for data, or attempts first to fill the buffer+     * with data from the MIDI hardware; this depends on the implementation.+     * We could call Pm_Poll here, but that would redo a lot of redundant+     * parameter checking, so I copied some code from Pm_Poll to here: */+    else err = (*(midi->dictionary->poll))(midi);++    if (err != pmNoError) {+        if (err == pmHostError) {+            midi->dictionary->check_host_error(midi);+        }+        return pm_errmsg(err);+    }++    while (n < length) {+        err = Pm_Dequeue(midi->queue, buffer++);+        if (err == pmBufferOverflow) {+            /* ignore the data we have retreived so far */+            return pm_errmsg(pmBufferOverflow);+        } else if (err == 0) { /* empty queue */+            break;+        }+        n++;+    }+    return n;+}++PMEXPORT PmError Pm_Poll(PortMidiStream *stream)+{+    PmInternal *midi = (PmInternal *) stream;+    PmError err;++    pm_hosterror = FALSE;+    /* arg checking */+    if(midi == NULL)+        err = pmBadPtr;+    else if (!pm_descriptors[midi->device_id].pub.opened)+        err = pmBadPtr;+    else if (!pm_descriptors[midi->device_id].pub.input)+        err = pmBadPtr;+    else+        err = (*(midi->dictionary->poll))(midi);++    if (err != pmNoError) {+        return pm_errmsg(err);+    }++    return (PmError) !Pm_QueueEmpty(midi->queue);+}+++/* this is called from Pm_Write and Pm_WriteSysEx to issue a+ * call to the system-dependent end_sysex function and handle + * the error return+ */+static PmError pm_end_sysex(PmInternal *midi)+{+    PmError err = (*midi->dictionary->end_sysex)(midi, 0);+    midi->sysex_in_progress = FALSE;+    return err;+}+++/* to facilitate correct error-handling, Pm_Write, Pm_WriteShort, and+   Pm_WriteSysEx all operate a state machine that "outputs" calls to+   write_short, begin_sysex, write_byte, end_sysex, and write_realtime */++PMEXPORT PmError Pm_Write(PortMidiStream *stream, PmEvent *buffer,+                          int32_t length)+{+    PmInternal *midi = (PmInternal *) stream;+    PmError err = pmNoError;+    int i;+    int bits;+    +    pm_hosterror = FALSE;+    /* arg checking */+    if (midi == NULL) {+        err = pmBadPtr;+    } else {+        descriptor_type desc = &pm_descriptors[midi->device_id]; +        if (!desc || !desc->pub.opened ||+            !desc->pub.output || !desc->pm_internal) {+            err = pmBadPtr;+        } else if (desc->pm_internal->is_removed) {+            err = pmDeviceRemoved;+        }+    }+    if (err != pmNoError) goto pm_write_error;+    +    if (midi->latency == 0) {+        midi->now = 0;+    } else {+        midi->now = (*(midi->time_proc))(midi->time_info);+        if (midi->first_message || midi->sync_time + 100 /*ms*/ < midi->now) {+            /* time to resync */+            midi->now = (*midi->dictionary->synchronize)(midi);+            midi->first_message = FALSE;+        }+    }+    /* error recovery: when a sysex is detected, we call+     *   dictionary->begin_sysex() followed by calls to+     *   dictionary->write_byte() and dictionary->write_realtime()+     *   until an end-of-sysex is detected, when we call+     *   dictionary->end_sysex(). After an error occurs, +     *   Pm_Write() continues to call functions. For example,+     *   it will continue to call write_byte() even after+     *   an error sending a sysex message, and end_sysex() will be+     *   called when an EOX or non-real-time status is found.+     * When errors are detected, Pm_Write() returns immediately, +     *   so it is possible that this will drop data and leave+     *   sysex messages in a partially transmitted state.+     */+    for (i = 0; i < length; i++) {+        uint32_t msg = buffer[i].message;+        bits = 0;+        /* is this a sysex message? */+        if (Pm_MessageStatus(msg) == MIDI_SYSEX) {+            if (midi->sysex_in_progress) {+                /* error: previous sysex was not terminated by EOX */+                midi->sysex_in_progress = FALSE;+                err = pmBadData;+                goto pm_write_error;+            }+            midi->sysex_in_progress = TRUE;+            if ((err = (*midi->dictionary->begin_sysex)(midi, +                               buffer[i].timestamp)) != pmNoError)+                goto pm_write_error;+            if ((err = (*midi->dictionary->write_byte)(midi, MIDI_SYSEX,+                               buffer[i].timestamp)) != pmNoError) +                goto pm_write_error;+            bits = 8;+            /* fall through to continue sysex processing */+        } else if ((msg & MIDI_STATUS_MASK) && +                   (Pm_MessageStatus(msg) != MIDI_EOX)) {+            /* a non-sysex message */+            if (midi->sysex_in_progress) {+                /* this should be a realtime message */+                if (is_real_time(msg)) {+                    if ((err = (*midi->dictionary->write_realtime)(midi, +                                       &(buffer[i]))) != pmNoError)+                        goto pm_write_error;+                } else {+                    midi->sysex_in_progress = FALSE;+                    err = pmBadData;+                    /* ignore any error from this, because we already have one */+                    /* pass 0 as timestamp -- it's ignored */+                    (*midi->dictionary->end_sysex)(midi, 0);+                    goto pm_write_error;+                }+            } else { /* regular short midi message */+                if ((err = (*midi->dictionary->write_short)(midi, +                                   &(buffer[i]))) != pmNoError)+                    goto pm_write_error;+                continue;+            }+        }+        if (midi->sysex_in_progress) { /* send sysex bytes until EOX */+            /* see if we can accelerate data transfer */+            if (bits == 0 && midi->fill_base && /* 4 bytes to copy */+                (*midi->fill_offset_ptr) + 4 <= midi->fill_length &&+                (msg & 0x80808080) == 0) { /* all data */+                    /* copy 4 bytes from msg to fill_base + fill_offset */+                    unsigned char *ptr = midi->fill_base + +                                         *(midi->fill_offset_ptr);+                    ptr[0] = msg; ptr[1] = msg >> 8; +                    ptr[2] = msg >> 16; ptr[3] = msg >> 24;+                    (*midi->fill_offset_ptr) += 4;+                     continue;+            }+            /* no acceleration, so do byte-by-byte copying */+            while (bits < 32) {+                unsigned char midi_byte = (unsigned char) (msg >> bits);+                if ((err = (*midi->dictionary->write_byte)(midi, midi_byte, +                                   buffer[i].timestamp)) != pmNoError)+                    goto pm_write_error;+                if (midi_byte == MIDI_EOX) {+                    err = pm_end_sysex(midi);+                    if (err != pmNoError) goto error_exit;+                    break; /* from while loop */+                }+                bits += 8;+            }+        } else {+            /* not in sysex mode, but message did not start with status */+            err = pmBadData;+            goto pm_write_error;+        }+    }+    /* after all messages are processed, send the data */+    if (!midi->sysex_in_progress)+        err = (*midi->dictionary->write_flush)(midi, 0);+pm_write_error:+    if (err == pmHostError) {+        midi->dictionary->check_host_error(midi);+    }+error_exit:+    return pm_errmsg(err);+}+++PMEXPORT PmError Pm_WriteShort(PortMidiStream *stream, PmTimestamp when,+                               PmMessage msg)+{+    PmEvent event;+    +    event.timestamp = when;+    event.message = msg;+    return Pm_Write(stream, &event, 1);+}+++PMEXPORT PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when, +                      unsigned char *msg)+{+    /* allocate buffer space for PM_DEFAULT_SYSEX_BUFFER_SIZE bytes */+    /* each PmEvent holds sizeof(PmMessage) bytes of sysex data */+    #define BUFLEN ((int) (PM_DEFAULT_SYSEX_BUFFER_SIZE / sizeof(PmMessage)))+    PmEvent buffer[BUFLEN];+    int buffer_size = 1; /* first time, send 1. After that, it's BUFLEN */+    PmInternal *midi = (PmInternal *) stream;+    PmError err = pmNoError;+    /* the next byte in the buffer is represented by an index, bufx, and+       a shift in bits */+    int shift = 0;+    int bufx = 0;+    buffer[0].message = 0;+    buffer[0].timestamp = when;++    while (1) {+        /* insert next byte into buffer */+        buffer[bufx].message |= ((*msg) << shift);+        shift += 8;+        if (*msg++ == MIDI_EOX) break;+        if (shift == 32) {+            shift = 0;+            bufx++;+            if (bufx == buffer_size) {+                err = Pm_Write(stream, buffer, buffer_size);+                /* note: Pm_Write has already called errmsg() */+                if (err) return err;+                /* prepare to fill another buffer */+                bufx = 0;+                buffer_size = BUFLEN;+                /* optimization: maybe we can just copy bytes */+                if (midi->fill_base) {+                    while (*(midi->fill_offset_ptr) < midi->fill_length) {+                        midi->fill_base[(*midi->fill_offset_ptr)++] = *msg;+                        if (*msg++ == MIDI_EOX) {+                            err = pm_end_sysex(midi);+                            if (err != pmNoError) return pm_errmsg(err);+                            goto end_of_sysex;+                        }+                    }+                    /* I thought that I could do a pm_Write here and+                     * change this if to a loop, avoiding calls in Pm_Write+                     * to the slower write_byte, but since +                     * sysex_in_progress is true, this will not flush+                     * the buffer, and we'll infinite loop: */+                    /* err = Pm_Write(stream, buffer, 0);+                       if (err) return err; */+                    /* instead, the way this works is that Pm_Write calls+                     * write_byte on 4 bytes. The first, since the buffer+                     * is full, will flush the buffer and allocate a new+                     * one. This primes the buffer so+                     * that we can return to the loop above and fill it+                     * efficiently without a lot of function calls.+                     */+                    buffer_size = 1; /* get another message started */+                }+            }+            buffer[bufx].message = 0;+            buffer[bufx].timestamp = when;+        } +        /* keep inserting bytes until you find MIDI_EOX */+    }+end_of_sysex:+    /* we're finished sending full buffers, but there may+     * be a partial one left.+     */+    if (shift != 0) bufx++; /* add partial message to buffer len */+    if (bufx) { /* bufx is number of PmEvents to send from buffer */+        err = Pm_Write(stream, buffer, bufx);+        if (err) return err;+    }+    return pmNoError;+}++++PmError pm_create_internal(PmInternal **stream, PmDeviceID device_id,+                           int is_input, int latency, PmTimeProcPtr time_proc,+                           void *time_info, int buffer_size)+{+    PmInternal *midi;  /* initialized below */+    if (device_id < 0 || device_id >= pm_descriptor_len) {+       return pmInvalidDeviceId;+    }+    if (latency < 0) {  /* force a legal value */+        latency = 0;+    }+    /* create portMidi internal data */+    midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); +    *stream = midi;+    if (!midi) {+        return pmInsufficientMemory;+    }+    midi->device_id = device_id;+    midi->is_input = is_input;+    midi->is_removed = FALSE;+    midi->time_proc = time_proc;+    /* if latency != 0, we need a time reference for output.+       we always need a time reference for input.+       If none is provided, use PortTime library */+    if (time_proc == NULL && (latency != 0 || is_input)) {+        if (!Pt_Started()) +            Pt_Start(1, 0, 0);+        /* time_get does not take a parameter, so coerce */+        midi->time_proc = (PmTimeProcPtr) Pt_Time;+    }+    midi->time_info = time_info;+    if (is_input) {+        midi->latency = 0;  /* unused by input */+        if (buffer_size <= 0) buffer_size = 256; /* default buffer size */+        midi->queue = Pm_QueueCreate(buffer_size, (int32_t) sizeof(PmEvent));+        if (!midi->queue) {+            /* free portMidi data */+            *stream = NULL;+            pm_free(midi);+            return pmInsufficientMemory;+        }+    } else {+        /* if latency zero, output immediate (timestamps ignored) */+        /* if latency < 0, use 0 but don't return an error */+        if (latency < 0) latency = 0;+        midi->latency = latency;+        midi->queue = NULL;  /* unused by output; input needs to allocate: */+    }+    midi->buffer_len = buffer_size; /* portMidi input storage */+    midi->sysex_in_progress = FALSE;+    midi->message = 0; +    midi->message_count = 0; +    midi->filters = (is_input ? PM_FILT_ACTIVE : 0);+    midi->channel_mask = 0xFFFF;+    midi->sync_time = 0;+    midi->first_message = TRUE;+    midi->api_info = NULL;+    midi->fill_base = NULL;+    midi->fill_offset_ptr = NULL;+    midi->fill_length = 0;+    midi->dictionary = pm_descriptors[device_id].dictionary;+    pm_descriptors[device_id].pm_internal = midi;+    return pmNoError;+}+++PMEXPORT PmError Pm_OpenInput(PortMidiStream** stream,+                              PmDeviceID inputDevice,+                              void *inputDriverInfo,+                              int32_t bufferSize,+                              PmTimeProcPtr time_proc,+                              void *time_info)+{+    PmInternal *midi;+    PmError err = pmNoError;+    pm_hosterror = FALSE;+    *stream = NULL;  /* invariant: *stream == midi */+    +    /* arg checking */+    if (!pm_descriptors[inputDevice].pub.input) +        err =  pmInvalidDeviceId;+    else if (pm_descriptors[inputDevice].pub.opened)+        err =  pmInvalidDeviceId;+    if (err != pmNoError) +        goto error_return;++    /* common initialization of PmInternal structure (midi): */+    err = pm_create_internal(&midi, inputDevice, TRUE, 0, time_proc,+                             time_info, bufferSize);+    if (err) {+        goto error_return;  /* will return with *stream == NULL */+    }+    *stream = midi;  /* no error, so midi is valid */++    /* open system dependent input device */+    err = (*midi->dictionary->open)(midi, inputDriverInfo);+    if (err) {+        *stream = NULL;+        pm_descriptors[inputDevice].pm_internal = NULL;+        /* free portMidi data */+        Pm_QueueDestroy(midi->queue);+        pm_free(midi);+    } else {+        /* portMidi input open successful */+        pm_descriptors[inputDevice].pub.opened = TRUE;+    }+error_return:+    /* note: if there is a pmHostError, it is the responsibility+     * of the system-dependent code (*midi->dictionary->open)()+     * to set pm_hosterror and pm_hosterror_text+     */+    return pm_errmsg(err);+}+++PMEXPORT PmError Pm_OpenOutput(PortMidiStream** stream,+                               PmDeviceID outputDevice,+                               void *outputDriverInfo,+                               int32_t bufferSize,+                               PmTimeProcPtr time_proc,+                               void *time_info,+                               int32_t latency)+{+    PmInternal *midi;+    PmError err = pmNoError;+    pm_hosterror = FALSE;+    *stream =  NULL;+    +    /* arg checking */+    if (outputDevice < 0 || outputDevice >= pm_descriptor_len)+        err = pmInvalidDeviceId;+    else if (!pm_descriptors[outputDevice].pub.output) +        err = pmInvalidDeviceId;+    else if (pm_descriptors[outputDevice].pub.opened)+        err = pmInvalidDeviceId;+    if (err != pmNoError) +        goto error_return;++    /* common initialization of PmInternal structure (midi): */+    err = pm_create_internal(&midi, outputDevice, FALSE, latency, time_proc,+                             time_info, bufferSize);+    *stream = midi;+    if (err) {+        goto error_return;+    }++    /* open system dependent output device */+    err = (*midi->dictionary->open)(midi, outputDriverInfo);+    if (err) {+        *stream = NULL;+        pm_descriptors[outputDevice].pm_internal = NULL;+        /* free portMidi data */+        pm_free(midi); +    } else {+        /* portMidi input open successful */+        pm_descriptors[outputDevice].pub.opened = TRUE;+    }+error_return:+    /* note: system-dependent code must set pm_hosterror and+     * pm_hosterror_text if a pmHostError occurs+     */+    return pm_errmsg(err);+}+++static PmError create_virtual_device(const char *name, const char *interf,+                                     void *device_info, int is_input)+{+    PmError err = pmNoError;+    int i;+    pm_hosterror = FALSE;+    +    /* arg checking */+    if (!name) {+        err = pmInvalidDeviceId;+        goto error_return;+    }++    Pm_Initialize();  /* just in case */++    /* create the virtual device */+    if (pm_interf_list_len == 0) {+        return pmNotImplemented;+    }+    if (!interf) {+        /* default interface is the first one */+        interf = pm_interf_list[0].interf;+    }+    /* look up and call the create_fn for interf(ace), e.g. "CoreMIDI" */+    for (i = 0; i < pm_interf_list_len; i++) {+        if (strcmp(pm_interf_list[i].interf, interf) == 0) {+            int id = (*pm_interf_list[i].create_fn)(is_input,+                                                        name, device_info);+            /* id could be pmNameConflict or an actual descriptor index */+            if (id >= 0) {+                pm_descriptors[id].pub.is_virtual = TRUE;+            }+            err = id;+            goto error_return;+        }+    }+    err = pmInterfaceNotSupported;++error_return:+    /* note: if there is a pmHostError, it is the responsibility+     * of the system-dependent code (*midi->dictionary->open)()+     * to set pm_hosterror and pm_hosterror_text+     */+    return pm_errmsg(err);+}+++PMEXPORT PmError Pm_CreateVirtualInput(const char *name,+                                       const char *interf,+                                       void *deviceInfo)+{+    return create_virtual_device(name, interf, deviceInfo, TRUE);+}++PMEXPORT PmError Pm_CreateVirtualOutput(const char *name, const char *interf,+                                        void *deviceInfo)+{+    return create_virtual_device(name, interf, deviceInfo, FALSE);+}++PmError Pm_DeleteVirtualDevice(PmDeviceID id)+{+    int i;+    const char *interf = pm_descriptors[id].pub.interf;+    PmError err = pmBadData;  /* returned if we cannot find the interface-+                               * specific delete function */+    /* arg checking */+    if (id < 0 || id >= pm_descriptor_len ||+        pm_descriptors[id].pub.opened || pm_descriptors[id].deleted) {+        return pmInvalidDeviceId;+    }+    /* delete function pointer is in interfaces list */+    for (i = 0; i < pm_interf_list_len; i++) {+        if (strcmp(pm_interf_list[i].interf, interf) == 0) {+            err = (*pm_interf_list[i].delete_fn)(id);+            break;+        }+    }+    pm_descriptors[id].deleted = TRUE;+    /* (pm_internal should already be NULL because !pub.opened) */+    pm_descriptors[id].pm_internal = NULL;+    pm_descriptors[id].descriptor = NULL;+    return err;+}++PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask)+{+    PmInternal *midi = (PmInternal *) stream;+    PmError err = pmNoError;++    if (midi == NULL)+        err = pmBadPtr;+    else+        midi->channel_mask = mask;++    return pm_errmsg(err);+}+++PMEXPORT PmError Pm_SetFilter(PortMidiStream *stream, int32_t filters)+{+    PmInternal *midi = (PmInternal *) stream;+    PmError err = pmNoError;++    /* arg checking */+    if (midi == NULL)+        err = pmBadPtr;+    else if (!pm_descriptors[midi->device_id].pub.opened)+        err = pmBadPtr;+    else+        midi->filters = filters;+    return pm_errmsg(err);+}+++PMEXPORT PmError Pm_Close(PortMidiStream *stream)+{+    PmInternal *midi = (PmInternal *) stream;+    PmError err = pmNoError;++    pm_hosterror = FALSE;+    /* arg checking */+    if (midi == NULL) /* midi must point to something */+        err = pmBadPtr;+    /* if it is an open device, the device_id will be valid */+    else if (midi->device_id < 0 || midi->device_id >= pm_descriptor_len)+        err = pmBadPtr;+    /* and the device should be in the opened state */+    else if (!pm_descriptors[midi->device_id].pub.opened)+        err = pmBadPtr;+    +    if (err != pmNoError) +        goto error_return;++    /* close the device */+    err = (*midi->dictionary->close)(midi);+    /* even if an error occurred, continue with cleanup */+    pm_descriptors[midi->device_id].pm_internal = NULL;+    pm_descriptors[midi->device_id].pub.opened = FALSE;+    if (midi->queue) Pm_QueueDestroy(midi->queue);+    pm_free(midi); +error_return:+    /* system dependent code must set pm_hosterror and+     * pm_hosterror_text if a pmHostError occurs.+     */+    return pm_errmsg(err);+}++PmError Pm_Synchronize(PortMidiStream* stream)+{+    PmInternal *midi = (PmInternal *) stream;+    PmError err = pmNoError;+    if (midi == NULL)+        err = pmBadPtr;+    else if (!pm_descriptors[midi->device_id].pub.output)+        err = pmBadPtr;+    else if (!pm_descriptors[midi->device_id].pub.opened)+        err = pmBadPtr;+    else+        midi->first_message = TRUE;+    return err;+}++PMEXPORT PmError Pm_Abort(PortMidiStream* stream)+{+    PmInternal *midi = (PmInternal *) stream;+    PmError err;+    /* arg checking */+    if (midi == NULL)+        err = pmBadPtr;+    else if (!pm_descriptors[midi->device_id].pub.output)+        err = pmBadPtr;+    else if (!pm_descriptors[midi->device_id].pub.opened)+        err = pmBadPtr;+    else+        err = (*midi->dictionary->abort)(midi);++    if (err == pmHostError) {+        midi->dictionary->check_host_error(midi);+    }+    return pm_errmsg(err);+}++++/* pm_channel_filtered returns non-zero if the channel mask is+   blocking the current channel */+#define pm_channel_filtered(status, mask) \+    ((((status) & 0xF0) != 0xF0) && (!(Pm_Channel((status) & 0x0F) & (mask))))+++/* The following two functions will checks to see if a MIDI message+   matches the filtering criteria.  Since the sysex routines only want+   to filter realtime messages, we need to have separate routines.+ */+++/* pm_realtime_filtered returns non-zero if the filter will kill the+   current message. Note that only realtime messages are checked here.+ */+#define pm_realtime_filtered(status, filters) \+    ((((status) & 0xF0) == 0xF0) && ((1 << ((status) & 0xF)) & (filters)))++/*+    return ((status == MIDI_ACTIVE) && (filters & PM_FILT_ACTIVE))+            ||  ((status == MIDI_CLOCK) && (filters & PM_FILT_CLOCK))+            ||  ((status == MIDI_START) && (filters & PM_FILT_PLAY))+            ||  ((status == MIDI_STOP) && (filters & PM_FILT_PLAY))+            ||  ((status == MIDI_CONTINUE) && (filters & PM_FILT_PLAY))+            ||  ((status == MIDI_F9) && (filters & PM_FILT_F9))+            ||  ((status == MIDI_FD) && (filters & PM_FILT_FD))+            ||  ((status == MIDI_RESET) && (filters & PM_FILT_RESET))+            ||  ((status == MIDI_MTC) && (filters & PM_FILT_MTC))+            ||  ((status == MIDI_SONGPOS) && (filters & PM_FILT_SONG_POSITION))+            ||  ((status == MIDI_SONGSEL) && (filters & PM_FILT_SONG_SELECT))+            ||  ((status == MIDI_TUNE) && (filters & PM_FILT_TUNE));+}*/+++/* pm_status_filtered returns non-zero if a filter will kill the+   current message, based on status. Note that sysex and real time are+   not checked.  It is up to the subsystem (winmm, core midi, alsa) to+   filter sysex, as it is handled more easily and efficiently at that+   level. Realtime message are filtered in pm_realtime_filtered.+ */+#define pm_status_filtered(status, filters) \+                ((1 << (16 + ((status) >> 4))) & (filters))+++/*+    return  ((status == MIDI_NOTE_ON) && (filters & PM_FILT_NOTE))+            ||  ((status == MIDI_NOTE_OFF) && (filters & PM_FILT_NOTE))+            ||  ((status == MIDI_CHANNEL_AT) &&+                 (filters & PM_FILT_CHANNEL_AFTERTOUCH))+            ||  ((status == MIDI_POLY_AT) && (filters & PM_FILT_POLY_AFTERTOUCH))+            ||  ((status == MIDI_PROGRAM) && (filters & PM_FILT_PROGRAM))+            ||  ((status == MIDI_CONTROL) && (filters & PM_FILT_CONTROL))+            ||  ((status == MIDI_PITCHBEND) && (filters & PM_FILT_PITCHBEND));++}+*/++static void pm_flush_sysex(PmInternal *midi, PmTimestamp timestamp)+{+    PmEvent event;+    +    /* there may be nothing in the buffer */+    if (midi->message_count == 0) return; /* nothing to flush */+    +    event.message = midi->message;+    event.timestamp = timestamp;+    /* copied from pm_read_short, avoids filtering */+    if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) {+        midi->sysex_in_progress = FALSE;+    }+    midi->message_count = 0;+    midi->message = 0;+}+++/* pm_read_short and pm_read_bytes+   are the interface between system-dependent MIDI input handlers+   and the system-independent PortMIDI code.+   The input handler MUST obey these rules:+   1) all short input messages must be sent to pm_read_short, which+      enqueues them to a FIFO for the application.+   2) each buffer of sysex bytes should be reported by calling pm_read_bytes+      (which sets midi->sysex_in_progress). After the eox byte, +      pm_read_bytes will clear sysex_in_progress+ */++/* pm_read_short is the place where all input messages arrive from +   system-dependent code such as pmwinmm.c. Here, the messages+   are entered into the PortMidi input buffer. + */+void pm_read_short(PmInternal *midi, PmEvent *event)+{ +    int status;+    /* arg checking */+    assert(midi != NULL);+    /* midi filtering is applied here */+    status = Pm_MessageStatus(event->message);+    if (!pm_status_filtered(status, midi->filters)+        && (!is_real_time(status) || +            !pm_realtime_filtered(status, midi->filters))+        && !pm_channel_filtered(status, midi->channel_mask)) {+        /* if sysex is in progress and we get a status byte, it had+           better be a realtime message or the starting SYSEX byte;+           otherwise, we exit the sysex_in_progress state+         */+        if (midi->sysex_in_progress && (status & MIDI_STATUS_MASK)) {+            /* two choices: real-time or not. If it's real-time, then+             * this should be delivered as a sysex byte because it is+             * embedded in a sysex message+             */+            if (is_real_time(status)) {+                midi->message |= (status << (8 * midi->message_count++));+                if (midi->message_count == 4) {+                    pm_flush_sysex(midi, event->timestamp);+                }+            } else { /* otherwise, it's not real-time. This interrupts+                      * a sysex message in progress */+                midi->sysex_in_progress = FALSE;+            }+        } else if (Pm_Enqueue(midi->queue, event) == pmBufferOverflow) {+            midi->sysex_in_progress = FALSE;+        }+    }+}+++/* pm_read_bytes -- a sequence of bytes has been read from a device.+ *        parse the bytes into PmEvents and put them in the queue.+ * midi - the midi device+ * data - the bytes+ * len - the number of bytes+ * timestamp - when were the bytes received?+ *+ * returns how many bytes processed, which is always the len parameter+ */+unsigned int pm_read_bytes(PmInternal *midi, const unsigned char *data, +                    int len, PmTimestamp timestamp)+{+    int i = 0; /* index into data, must not be unsigned (!) */+    PmEvent event;+    event.timestamp = timestamp;+    assert(midi);++    /* Since sysex messages may have embedded real-time messages, we+     * cannot simply send every consecutive group of 4 bytes as sysex+     * data. Instead, we insert each data byte into midi->message and+     * keep count using midi->message_count. If we encounter a+     * real-time message, it is sent immediately as a 1-byte PmEvent,+     * while sysex bytes are sent as PmEvents in groups of 4 bytes+     * until the sysex is either terminated by EOX (F7) or a+     * non-real-time message is encountered, indicating that the EOX+     * was dropped.+     */++    /* This is a finite state machine so that we can accept any number+     * of bytes, even if they contain partial messages.+     *+     * midi->sysex_in_progress says we are expecting sysex message bytes+     *    (otherwise, expect a short message or real-time message)+     * midi->message accumulates bytes to enqueue for application+     * midi->message_count is the number of bytes accumulated+     * midi->short_message_count is how many bytes we need in midi->message,+     *    therefore midi->message_count, before we have a complete message+     * midi->running_status is running status or 0 if there is none+     *+     * Set running status when: A status byte < F0 is received.+     * Clear running status when: A status byte from F0 through F7 is+     * received.+     * Ignore (drop) data bytes when running status is 0.+     *+     * Our output buffer (the application input buffer) can overflow+     * at any time. If that occurs, we have to clear sysex_in_progress+     * (otherwise, the buffer could be flushed and we could resume+     * inserting sysex bytes into the buffer, resulting in a continuation+     * of a sysex message even though a buffer full of bytes was dropped.)+     *+     * Since we have to parse everything and form <=4-byte PmMessages,+     * we send all messages via pm_read_short, which filters messages+     * according to midi->filters and clears sysex_in_progress on+     * buffer overflow. This also provides a "short cut" for short+     * messages that are already parsed, allowing API-specific code+     * to bypass this more expensive state machine. What if we are+     * getting a sysex message, but it is interrupted by a short+     * message (status 80-EF) and a direct call to pm_read_short?+     * Without some care, the state machine would still be in+     * sysex_in_progress mode, and subsequent data bytes would be+     * accumulated as more sysex data, which is wrong since you+     * cannot have a short message in the middle of a sysex message.+     * To avoid this problem, pm_read_short clears sysex_in_progress+     * when a non-real-time short message arrives.+     */++    while (i < len) {+        unsigned char byte = data[i++];+        if (is_real_time(byte)) {+            event.message = byte;+            pm_read_short(midi, &event);+        } else if (byte & MIDI_STATUS_MASK && byte != MIDI_EOX) {+            midi->message = byte;+            midi->message_count = 1;+            if (byte == MIDI_SYSEX) {+                midi->sysex_in_progress = TRUE;+            } else {+                /* If Sysex is in progress, we cancel it because we encountered+                   a status byte:+                */+                midi->sysex_in_progress = FALSE;+                midi->short_message_count = pm_midi_length(midi->message);+                /* maybe we're done already with a 1-byte message: */+                if (midi->short_message_count == 1) {+                    event.message = byte;+                    pm_read_short(midi, &event);+                    midi->message_count = 0;+                }+            }+        } else if (midi->sysex_in_progress) {  /* sysex data byte */+            /* accumulate sysex message data or EOX */+            midi->message |= (byte << (8 * midi->message_count++));+            if (midi->message_count == 4 || byte == MIDI_EOX) {+                event.message = midi->message;+                /* enqueue if not filtered, and then if there is overflow,+                   stop sysex_in_progress */+                if (!(midi->filters & PM_FILT_SYSEX) &&+                    Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) {+                    midi->sysex_in_progress = FALSE;+                } else if (byte == MIDI_EOX) {  /* continue unless EOX */+                    midi->sysex_in_progress = FALSE;+                }+                midi->message_count = 0;+                midi->message = 0;+            }+        } else {  /* no sysex in progress, must be short message */+            if (midi->message_count == 0) {  /* need a running status */+                if (midi->running_status) {+                    midi->message = midi->running_status;+                    midi->message_count = 1;+                } else {  /* drop data byte - not sysex and no status byte */+                    continue;+                }+            }+            midi->message |= (byte << (8 * midi->message_count++));+            if (midi->message_count == midi->short_message_count) {+                event.message = midi->message;+                pm_read_short(midi, &event);+            }+        }+    }+    return i;+}
portmidi/pm_common/portmidi.h view
@@ -1,612 +1,1009 @@-#ifndef PORT_MIDI_H
-#define PORT_MIDI_H
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-/*
- * PortMidi Portable Real-Time MIDI Library
- * PortMidi API Header File
- * Latest version available at: http://www.cs.cmu.edu/~music/portmidi/
- *
- * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
- * Copyright (c) 2001-2006 Roger B. Dannenberg
- *
- * Latest version available at: http://www.cs.cmu.edu/~music/portmidi/
- *
- * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
- * Copyright (c) 2001-2006 Roger B. Dannenberg
- *
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files
- * (the "Software"), to deal in the Software without restriction,
- * including without limitation the rights to use, copy, modify, merge,
- * publish, distribute, sublicense, and/or sell copies of the Software,
- * and to permit persons to whom the Software is furnished to do so,
- * subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
- * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
- * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
-/*
- * The text above constitutes the entire PortMidi license; however, 
- * the PortMusic community also makes the following non-binding requests:
- *
- * Any person wishing to distribute modifications to the Software is
- * requested to send the modifications to the original developer so that
- * they can be incorporated into the canonical version. It is also
- * requested that these non-binding requests be included along with the 
- * license above.
- */
-
-/* CHANGELOG FOR PORTMIDI
- *     (see ../CHANGELOG.txt)
- *
- * IMPORTANT INFORMATION ABOUT A WIN32 BUG:
- *
- *    Windows apparently has a serious midi bug -- if you do not close ports, Windows
- *    may crash. PortMidi tries to protect against this by using a DLL to clean up.
- *
- *    If client exits for example with:
- *      i)  assert 
- *      ii) Ctrl^c, 
- *    then DLL clean-up routine called. However, when client does something 
- *    really bad (e.g. assigns value to NULL pointer) then DLL CLEANUP ROUTINE
- *    NEVER RUNS! In this state, if you wait around long enough, you will
- *    probably get the blue screen of death. Can also go into Pview and there will
- *    exist zombie process that you can't kill.
- *
- *    You can enable the DLL cleanup routine by defining USE_DLL_FOR_CLEANUP.
- *    Do not define this preprocessor symbol if you do not want to use this
- *    feature.
- *
- * NOTES ON HOST ERROR REPORTING: 
- *
- *    PortMidi errors (of type PmError) are generic, system-independent errors.
- *    When an error does not map to one of the more specific PmErrors, the
- *    catch-all code pmHostError is returned. This means that PortMidi has
- *    retained a more specific system-dependent error code. The caller can
- *    get more information by calling Pm_HasHostError() to test if there is
- *    a pending host error, and Pm_GetHostErrorText() to get a text string
- *    describing the error. Host errors are reported on a per-device basis 
- *    because only after you open a device does PortMidi have a place to 
- *    record the host error code. I.e. only 
- *    those routines that receive a (PortMidiStream *) argument check and 
- *    report errors. One exception to this is that Pm_OpenInput() and 
- *    Pm_OpenOutput() can report errors even though when an error occurs,
- *    there is no PortMidiStream* to hold the error. Fortunately, both
- *    of these functions return any error immediately, so we do not really
- *    need per-device error memory. Instead, any host error code is stored
- *    in a global, pmHostError is returned, and the user can call 
- *    Pm_GetHostErrorText() to get the error message (and the invalid stream
- *    parameter will be ignored.) The functions 
- *    pm_init and pm_term do not fail or raise
- *    errors. The job of pm_init is to locate all available devices so that
- *    the caller can get information via PmDeviceInfo(). If an error occurs,
- *    the device is simply not listed as available.
- *
- *    Host errors come in two flavors:
- *      a) host error 
- *      b) host error during callback
- *    These can occur w/midi input or output devices. (b) can only happen 
- *    asynchronously (during callback routines), whereas (a) only occurs while
- *    synchronously running PortMidi and any resulting system dependent calls.
- *    Both (a) and (b) are reported by the next read or write call. You can
- *    also query for asynchronous errors (b) at any time by calling
- *    Pm_HasHostError().
- *
- * NOTES ON COMPILE-TIME SWITCHES
- *
- *    DEBUG assumes stdio and a console. Use this if you want automatic, simple
- *        error reporting, e.g. for prototyping. If you are using MFC or some 
- *        other graphical interface with no console, DEBUG probably should be
- *        undefined.
- *    PM_CHECK_ERRORS more-or-less takes over error checking for return values,
- *        stopping your program and printing error messages when an error
- *        occurs. This also uses stdio for console text I/O.
- *    USE_DLL_FOR_CLEANUP is described above. (Windows only.)
- *    
- */ 
-
-#ifndef FALSE
-    #define FALSE 0
-#endif
-#ifndef TRUE
-    #define TRUE 1
-#endif
-
-/* default size of buffers for sysex transmission: */
-#define PM_DEFAULT_SYSEX_BUFFER_SIZE 1024
-
-
-typedef enum {
-    pmNoError = 0,
-    pmNoData = 0, /* A "no error" return that also indicates no data avail. */
-    pmGotData = 1, /* A "no error" return that also indicates data available */
-    pmHostError = -10000,
-    pmInvalidDeviceId, /* out of range or 
-                        * output device when input is requested or 
-                        * input device when output is requested or
-                        * device is already opened 
-                        */
-    pmInsufficientMemory,
-    pmBufferTooSmall,
-    pmBufferOverflow,
-    pmBadPtr,
-    pmBadData, /* illegal midi data, e.g. missing EOX */
-    pmInternalError,
-    pmBufferMaxSize /* buffer is already as large as it can be */
-    /* NOTE: If you add a new error type, be sure to update Pm_GetErrorText() */
-} PmError;
-
-/*
-    Pm_Initialize() is the library initialisation function - call this before
-    using the library.
-*/
-
-PmError Pm_Initialize( void );
-
-/*
-    Pm_Terminate() is the library termination function - call this after
-    using the library.
-*/
-
-PmError Pm_Terminate( void );
-
-/*  A single PortMidiStream is a descriptor for an open MIDI device.
-*/
-typedef void PortMidiStream;
-#define PmStream PortMidiStream
-
-/*
-    Test whether stream has a pending host error. Normally, the client finds
-    out about errors through returned error codes, but some errors can occur
-    asynchronously where the client does not
-    explicitly call a function, and therefore cannot receive an error code.
-    The client can test for a pending error using Pm_HasHostError(). If true,
-    the error can be accessed and cleared by calling Pm_GetErrorText(). 
-    Errors are also cleared by calling other functions that can return
-    errors, e.g. Pm_OpenInput(), Pm_OpenOutput(), Pm_Read(), Pm_Write(). The
-    client does not need to call Pm_HasHostError(). Any pending error will be
-    reported the next time the client performs an explicit function call on 
-    the stream, e.g. an input or output operation. Until the error is cleared,
-    no new error codes will be obtained, even for a different stream.
-*/
-int Pm_HasHostError( PortMidiStream * stream );
-
-
-/*  Translate portmidi error number into human readable message.
-    These strings are constants (set at compile time) so client has 
-    no need to allocate storage
-*/
-const char *Pm_GetErrorText( PmError errnum );
-
-/*  Translate portmidi host error into human readable message.
-    These strings are computed at run time, so client has to allocate storage.
-    After this routine executes, the host error is cleared. 
-*/
-void Pm_GetHostErrorText(char * msg, unsigned int len);
-
-#define HDRLENGTH 50
-#define PM_HOST_ERROR_MSG_LEN 256u /* any host error msg will occupy less 
-                                      than this number of characters */
-
-/*
-    Device enumeration mechanism.
-
-    Device ids range from 0 to Pm_CountDevices()-1.
-
-*/
-typedef int PmDeviceID;
-#define pmNoDevice -1
-typedef struct {
-    int structVersion; 
-    const char *interf; /* underlying MIDI API, e.g. MMSystem or DirectX */
-    const char *name;   /* device name, e.g. USB MidiSport 1x1 */
-    int input; /* true iff input is available */
-    int output; /* true iff output is available */
-    int opened; /* used by generic PortMidi code to do error checking on arguments */
-
-} PmDeviceInfo;
-
-
-int Pm_CountDevices( void );
-/*
-    Pm_GetDefaultInputDeviceID(), Pm_GetDefaultOutputDeviceID()
-
-    Return the default device ID or pmNoDevice if there are no devices.
-    The result can be passed to Pm_OpenMidi().
-    
-    On the PC, the user can specify a default device by
-    setting an environment variable. For example, to use device #1.
-
-        set PM_RECOMMENDED_OUTPUT_DEVICE=1
-    
-    The user should first determine the available device ID by using
-    the supplied application "testin" or "testout".
-
-    In general, the registry is a better place for this kind of info,
-    and with USB devices that can come and go, using integers is not
-    very reliable for device identification. Under Windows, if
-    PM_RECOMMENDED_OUTPUT_DEVICE (or PM_RECOMMENDED_INPUT_DEVICE) is
-    *NOT* found in the environment, then the default device is obtained
-    by looking for a string in the registry under:
-        HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Input_Device
-    and HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Output_Device
-    for a string. The number of the first device with a substring that
-    matches the string exactly is returned. For example, if the string
-    in the registry is "USB", and device 1 is named 
-    "In USB MidiSport 1x1", then that will be the default
-    input because it contains the string "USB".
-
-    In addition to the name, PmDeviceInfo has the member "interf", which
-    is the interface name. (The "interface" is the underlying software
-    system or API used by PortMidi to access devices. Examples are 
-    MMSystem, DirectX (not implemented), ALSA, OSS (not implemented), etc.)
-    At present, the only Win32 interface is "MMSystem", the only Linux
-    interface is "ALSA", and the only Max OS X interface is "CoreMIDI".
-    To specify both the interface and the device name in the registry,
-    separate the two with a comma and a space, e.g.:
-        MMSystem, In USB MidiSport 1x1
-    In this case, the string before the comma must be a substring of
-    the "interf" string, and the string after the space must be a 
-    substring of the "name" name string in order to match the device.
-
-    Note: in the current release, the default is simply the first device
-    (the input or output device with the lowest PmDeviceID).
-*/
-PmDeviceID Pm_GetDefaultInputDeviceID( void );
-PmDeviceID Pm_GetDefaultOutputDeviceID( void );
-
-/*
-    PmTimestamp is used to represent a millisecond clock with arbitrary
-    start time. The type is used for all MIDI timestampes and clocks.
-*/
-typedef long PmTimestamp;
-typedef PmTimestamp (*PmTimeProcPtr)(void *time_info);
-
-/* TRUE if t1 before t2 */
-#define PmBefore(t1,t2) ((t1-t2) < 0)
-
-/*
-    Pm_GetDeviceInfo() returns a pointer to a PmDeviceInfo structure
-    referring to the device specified by id.
-    If id is out of range the function returns NULL.
-
-    The returned structure is owned by the PortMidi implementation and must
-    not be manipulated or freed. The pointer is guaranteed to be valid
-    between calls to Pm_Initialize() and Pm_Terminate().
-*/
-const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id );
-
-/*
-    Pm_OpenInput() and Pm_OpenOutput() open devices.
-
-    stream is the address of a PortMidiStream pointer which will receive
-    a pointer to the newly opened stream.
-
-    inputDevice is the id of the device used for input (see PmDeviceID above).
-
-    inputDriverInfo is a pointer to an optional driver specific data structure
-    containing additional information for device setup or handle processing.
-    inputDriverInfo is never required for correct operation. If not used
-    inputDriverInfo should be NULL.
-
-    outputDevice is the id of the device used for output (see PmDeviceID above.)
-
-    outputDriverInfo is a pointer to an optional driver specific data structure
-    containing additional information for device setup or handle processing.
-    outputDriverInfo is never required for correct operation. If not used
-    outputDriverInfo should be NULL.
-
-    For input, the buffersize specifies the number of input events to be 
-    buffered waiting to be read using Pm_Read(). For output, buffersize 
-    specifies the number of output events to be buffered waiting for output. 
-    (In some cases -- see below -- PortMidi does not buffer output at all
-    and merely passes data to a lower-level API, in which case buffersize
-    is ignored.)
-    
-    latency is the delay in milliseconds applied to timestamps to determine 
-    when the output should actually occur. (If latency is < 0, 0 is assumed.) 
-    If latency is zero, timestamps are ignored and all output is delivered
-    immediately. If latency is greater than zero, output is delayed until the
-    message timestamp plus the latency. (NOTE: the time is measured relative 
-    to the time source indicated by time_proc. Timestamps are absolute,
-    not relative delays or offsets.) In some cases, PortMidi can obtain
-    better timing than your application by passing timestamps along to the
-    device driver or hardware. Latency may also help you to synchronize midi
-    data to audio data by matching midi latency to the audio buffer latency.
-
-    time_proc is a pointer to a procedure that returns time in milliseconds. It
-    may be NULL, in which case a default millisecond timebase (PortTime) is 
-    used. If the application wants to use PortTime, it should start the timer
-    (call Pt_Start) before calling Pm_OpenInput or Pm_OpenOutput. If the
-    application tries to start the timer *after* Pm_OpenInput or Pm_OpenOutput,
-    it may get a ptAlreadyStarted error from Pt_Start, and the application's
-    preferred time resolution and callback function will be ignored.
-    time_proc result values are appended to incoming MIDI data, and time_proc
-    times are used to schedule outgoing MIDI data (when latency is non-zero).
-
-    time_info is a pointer passed to time_proc.
-
-    Example: If I provide a timestamp of 5000, latency is 1, and time_proc
-    returns 4990, then the desired output time will be when time_proc returns
-    timestamp+latency = 5001. This will be 5001-4990 = 11ms from now.
-
-    return value:
-    Upon success Pm_Open() returns PmNoError and places a pointer to a
-    valid PortMidiStream in the stream argument.
-    If a call to Pm_Open() fails a nonzero error code is returned (see
-    PMError above) and the value of port is invalid.
-
-    Any stream that is successfully opened should eventually be closed
-    by calling Pm_Close().
-
-*/
-PmError Pm_OpenInput( PortMidiStream** stream,
-                PmDeviceID inputDevice,
-                void *inputDriverInfo,
-                long bufferSize,
-                PmTimeProcPtr time_proc,
-                void *time_info );
-
-PmError Pm_OpenOutput( PortMidiStream** stream,
-                PmDeviceID outputDevice,
-                void *outputDriverInfo,
-                long bufferSize,
-                PmTimeProcPtr time_proc,
-                void *time_info,
-                long latency );
-
-/* 
-    Pm_SetFilter() sets filters on an open input stream to drop selected
-    input types. By default, only active sensing messages are filtered.
-    To prohibit, say, active sensing and sysex messages, call
-    Pm_SetFilter(stream, PM_FILT_ACTIVE | PM_FILT_SYSEX);
-
-    Filtering is useful when midi routing or midi thru functionality is being
-    provided by the user application.
-    For example, you may want to exclude timing messages (clock, MTC, start/stop/continue),
-    while allowing note-related messages to pass.
-    Or you may be using a sequencer or drum-machine for MIDI clock information but want to
-    exclude any notes it may play.
- */
-
-/* filter active sensing messages (0xFE): */
-#define PM_FILT_ACTIVE (1 << 0x0E)
-/* filter system exclusive messages (0xF0): */
-#define PM_FILT_SYSEX (1 << 0x00)
-/* filter MIDI clock message (0xF8) */
-#define PM_FILT_CLOCK (1 << 0x08)
-/* filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */
-#define PM_FILT_PLAY ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B))
-/* filter tick messages (0xF9) */
-#define PM_FILT_TICK (1 << 0x09)
-/* filter undefined FD messages */
-#define PM_FILT_FD (1 << 0x0D)
-/* filter undefined real-time messages */
-#define PM_FILT_UNDEFINED PM_FILT_FD
-/* filter reset messages (0xFF) */
-#define PM_FILT_RESET (1 << 0x0F)
-/* filter all real-time messages */
-#define PM_FILT_REALTIME (PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK | \
-    PM_FILT_PLAY | PM_FILT_UNDEFINED | PM_FILT_RESET | PM_FILT_TICK)
-/* filter note-on and note-off (0x90-0x9F and 0x80-0x8F */
-#define PM_FILT_NOTE ((1 << 0x19) | (1 << 0x18))
-/* filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/
-#define PM_FILT_CHANNEL_AFTERTOUCH (1 << 0x1D)
-/* per-note aftertouch (0xA0-0xAF) */
-#define PM_FILT_POLY_AFTERTOUCH (1 << 0x1A)
-/* filter both channel and poly aftertouch */
-#define PM_FILT_AFTERTOUCH (PM_FILT_CHANNEL_AFTERTOUCH | PM_FILT_POLY_AFTERTOUCH)
-/* Program changes (0xC0-0xCF) */
-#define PM_FILT_PROGRAM (1 << 0x1C)
-/* Control Changes (CC's) (0xB0-0xBF)*/
-#define PM_FILT_CONTROL (1 << 0x1B)
-/* Pitch Bender (0xE0-0xEF*/
-#define PM_FILT_PITCHBEND (1 << 0x1E)
-/* MIDI Time Code (0xF1)*/
-#define PM_FILT_MTC (1 << 0x01)
-/* Song Position (0xF2) */
-#define PM_FILT_SONG_POSITION (1 << 0x02)
-/* Song Select (0xF3)*/
-#define PM_FILT_SONG_SELECT (1 << 0x03)
-/* Tuning request (0xF6)*/
-#define PM_FILT_TUNE (1 << 0x06)
-/* All System Common messages (mtc, song position, song select, tune request) */
-#define PM_FILT_SYSTEMCOMMON (PM_FILT_MTC | PM_FILT_SONG_POSITION | PM_FILT_SONG_SELECT | PM_FILT_TUNE)
-
-
-PmError Pm_SetFilter( PortMidiStream* stream, long filters );
-
-/*
-    Pm_SetChannelMask() filters incoming messages based on channel.
-    The mask is a 16-bit bitfield corresponding to appropriate channels
-    The Pm_Channel macro can assist in calling this function.
-    i.e. to set receive only input on channel 1, call with
-    Pm_SetChannelMask(Pm_Channel(1));
-    Multiple channels should be OR'd together, like
-    Pm_SetChannelMask(Pm_Channel(10) | Pm_Channel(11))
-
-    All channels are allowed by default
-*/
-#define Pm_Channel(channel) (1<<(channel))
-
-PmError Pm_SetChannelMask(PortMidiStream *stream, int mask);
-
-/*
-    Pm_Abort() terminates outgoing messages immediately
-    The caller should immediately close the output port;
-    this call may result in transmission of a partial midi message.
-    There is no abort for Midi input because the user can simply
-    ignore messages in the buffer and close an input device at
-    any time.
- */
-PmError Pm_Abort( PortMidiStream* stream );
-     
-/*
-    Pm_Close() closes a midi stream, flushing any pending buffers.
-    (PortMidi attempts to close open streams when the application 
-    exits -- this is particularly difficult under Windows.)
-*/
-PmError Pm_Close( PortMidiStream* stream );
-
-/*
-    Pm_Message() encodes a short Midi message into a long word. If data1
-    and/or data2 are not present, use zero.
-
-    Pm_MessageStatus(), Pm_MessageData1(), and 
-    Pm_MessageData2() extract fields from a long-encoded midi message.
-*/
-#define Pm_Message(status, data1, data2) \
-         ((((data2) << 16) & 0xFF0000) | \
-          (((data1) << 8) & 0xFF00) | \
-          ((status) & 0xFF))
-#define Pm_MessageStatus(msg) ((msg) & 0xFF)
-#define Pm_MessageData1(msg) (((msg) >> 8) & 0xFF)
-#define Pm_MessageData2(msg) (((msg) >> 16) & 0xFF)
-
-/* All midi data comes in the form of PmEvent structures. A sysex
-   message is encoded as a sequence of PmEvent structures, with each
-   structure carrying 4 bytes of the message, i.e. only the first
-   PmEvent carries the status byte.
-
-   Note that MIDI allows nested messages: the so-called "real-time" MIDI 
-   messages can be inserted into the MIDI byte stream at any location, 
-   including within a sysex message. MIDI real-time messages are one-byte
-   messages used mainly for timing (see the MIDI spec). PortMidi retains 
-   the order of non-real-time MIDI messages on both input and output, but 
-   it does not specify exactly how real-time messages are processed. This
-   is particulary problematic for MIDI input, because the input parser 
-   must either prepare to buffer an unlimited number of sysex message 
-   bytes or to buffer an unlimited number of real-time messages that 
-   arrive embedded in a long sysex message. To simplify things, the input
-   parser is allowed to pass real-time MIDI messages embedded within a 
-   sysex message, and it is up to the client to detect, process, and 
-   remove these messages as they arrive.
-
-   When receiving sysex messages, the sysex message is terminated
-   by either an EOX status byte (anywhere in the 4 byte messages) or
-   by a non-real-time status byte in the low order byte of the message.
-   If you get a non-real-time status byte but there was no EOX byte, it 
-   means the sysex message was somehow truncated. This is not
-   considered an error; e.g., a missing EOX can result from the user
-   disconnecting a MIDI cable during sysex transmission.
-
-   A real-time message can occur within a sysex message. A real-time 
-   message will always occupy a full PmEvent with the status byte in 
-   the low-order byte of the PmEvent message field. (This implies that
-   the byte-order of sysex bytes and real-time message bytes may not
-   be preserved -- for example, if a real-time message arrives after
-   3 bytes of a sysex message, the real-time message will be delivered
-   first. The first word of the sysex message will be delivered only
-   after the 4th byte arrives, filling the 4-byte PmEvent message field.
-   
-   The timestamp field is observed when the output port is opened with
-   a non-zero latency. A timestamp of zero means "use the current time",
-   which in turn means to deliver the message with a delay of
-   latency (the latency parameter used when opening the output port.)
-   Do not expect PortMidi to sort data according to timestamps -- 
-   messages should be sent in the correct order, and timestamps MUST 
-   be non-decreasing. See also "Example" for Pm_OpenOutput() above.
-
-   A sysex message will generally fill many PmEvent structures. On 
-   output to a PortMidiStream with non-zero latency, the first timestamp
-   on sysex message data will determine the time to begin sending the 
-   message. PortMidi implementations may ignore timestamps for the 
-   remainder of the sysex message. 
-   
-   On input, the timestamp ideally denotes the arrival time of the 
-   status byte of the message. The first timestamp on sysex message 
-   data will be valid. Subsequent timestamps may denote 
-   when message bytes were actually received, or they may be simply 
-   copies of the first timestamp.
-
-   Timestamps for nested messages: If a real-time message arrives in 
-   the middle of some other message, it is enqueued immediately with 
-   the timestamp corresponding to its arrival time. The interrupted 
-   non-real-time message or 4-byte packet of sysex data will be enqueued 
-   later. The timestamp of interrupted data will be equal to that of
-   the interrupting real-time message to insure that timestamps are
-   non-decreasing.
- */
-typedef long PmMessage;
-typedef struct {
-    PmMessage      message;
-    PmTimestamp    timestamp;
-} PmEvent;
-
-/*
-    Pm_Read() retrieves midi data into a buffer, and returns the number
-    of events read. Result is a non-negative number unless an error occurs, 
-    in which case a PmError value will be returned.
-
-    Buffer Overflow
-
-    The problem: if an input overflow occurs, data will be lost, ultimately 
-    because there is no flow control all the way back to the data source. 
-    When data is lost, the receiver should be notified and some sort of 
-    graceful recovery should take place, e.g. you shouldn't resume receiving 
-    in the middle of a long sysex message.
-
-    With a lock-free fifo, which is pretty much what we're stuck with to 
-    enable portability to the Mac, it's tricky for the producer and consumer 
-    to synchronously reset the buffer and resume normal operation.
-
-    Solution: the buffer managed by PortMidi will be flushed when an overflow
-    occurs. The consumer (Pm_Read()) gets an error message (pmBufferOverflow)
-    and ordinary processing resumes as soon as a new message arrives. The
-    remainder of a partial sysex message is not considered to be a "new
-    message" and will be flushed as well.
-
-*/
-int Pm_Read( PortMidiStream *stream, PmEvent *buffer, long length );
-
-/*
-    Pm_Poll() tests whether input is available, 
-    returning TRUE, FALSE, or an error value.
-*/
-PmError Pm_Poll( PortMidiStream *stream);
-
-/* 
-    Pm_Write() writes midi data from a buffer. This may contain:
-        - short messages 
-    or 
-        - sysex messages that are converted into a sequence of PmEvent
-          structures, e.g. sending data from a file or forwarding them
-          from midi input.
-
-    Use Pm_WriteSysEx() to write a sysex message stored as a contiguous 
-    array of bytes.
-
-    Sysex data may contain embedded real-time messages.
-*/
-PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length );
-
-/*
-    Pm_WriteShort() writes a timestamped non-system-exclusive midi message.
-    Messages are delivered in order as received, and timestamps must be 
-    non-decreasing. (But timestamps are ignored if the stream was opened
-    with latency = 0.)
-*/
-PmError Pm_WriteShort( PortMidiStream *stream, PmTimestamp when, long msg);
-
-/*
-    Pm_WriteSysEx() writes a timestamped system-exclusive midi message.
-*/
-PmError Pm_WriteSysEx( PortMidiStream *stream, PmTimestamp when, unsigned char *msg);
-
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
-#endif /* PORT_MIDI_H */
+#ifndef PORTMIDI_PORTMIDI_H+#define PORTMIDI_PORTMIDI_H++#ifdef __cplusplus+extern "C" {+#endif /* __cplusplus */++/*+ * PortMidi Portable Real-Time MIDI Library+ * PortMidi API Header File+ * Latest version available at: http://sourceforge.net/projects/portmedia+ *+ * Copyright (c) 1999-2000 Ross Bencina and Phil Burk+ * Copyright (c) 2001-2006 Roger B. Dannenberg+ *+ * Permission is hereby granted, free of charge, to any person obtaining+ * a copy of this software and associated documentation files+ * (the "Software"), to deal in the Software without restriction,+ * including without limitation the rights to use, copy, modify, merge,+ * publish, distribute, sublicense, and/or sell copies of the Software,+ * and to permit persons to whom the Software is furnished to do so,+ * subject to the following conditions:+ *+ * The above copyright notice and this permission notice shall be+ * included in all copies or substantial portions of the Software.+ *+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.+ */++/*+ * The text above constitutes the entire PortMidi license; however, + * the PortMusic community also makes the following non-binding requests:+ *+ * Any person wishing to distribute modifications to the Software is+ * requested to send the modifications to the original developer so that+ * they can be incorporated into the canonical version. It is also+ * requested that these non-binding requests be included along with the + * license above.+ */++/* CHANGELOG FOR PORTMIDI+ *     (see ../CHANGELOG.txt)+ *+ * NOTES ON HOST ERROR REPORTING: + *+ *    PortMidi errors (of type PmError) are generic,+ *    system-independent errors.  When an error does not map to one of+ *    the more specific PmErrors, the catch-all code pmHostError is+ *    returned. This means that PortMidi has retained a more specific+ *    system-dependent error code. The caller can get more information+ *    by calling Pm_GetHostErrorText() to get a text string describing+ *    the error. Host errors can arise asynchronously from callbacks,+ *    * so there is no specific return code. Asynchronous errors are+ *    checked and reported by Pm_Poll. You can also check by calling+ *    Pm_HasHostError().  If this returns TRUE, Pm_GetHostErrorText()+ *    will return a text description of the error.+ *+ * NOTES ON COMPILE-TIME SWITCHES+ *+ *    DEBUG assumes stdio and a console. Use this if you want+ *        automatic, simple error reporting, e.g. for prototyping. If+ *        you are using MFC or some other graphical interface with no+ *        console, DEBUG probably should be undefined.+ *    PM_CHECK_ERRORS more-or-less takes over error checking for+ *        return values, stopping your program and printing error+ *        messages when an error occurs. This also uses stdio for+ *        console text I/O. You can selectively disable this error+ *        checking by declaring extern int pm_check_errors; and + *        setting pm_check_errors = FALSE; You can also reenable.+ */+/** +    \defgroup grp_basics Basic Definitions+    @{+*/++#include <stdint.h>++#ifdef _WINDLL+#define PMEXPORT __declspec(dllexport)+#else+#define PMEXPORT +#endif++#ifndef FALSE+    #define FALSE 0+#endif+#ifndef TRUE+    #define TRUE 1+#endif++/** default size of buffers for sysex transmission: */+#define PM_DEFAULT_SYSEX_BUFFER_SIZE 1024+++typedef enum {+    pmNoError = 0, /**< Normal return value indicating no error. */+    pmNoData = 0, /**< @brief No error, also indicates no data available.+                   * Use this constant where a value greater than zero would+                   * indicate data is available.+                   */+    pmGotData = 1, /**< A "no error" return also indicating data available. */+    pmHostError = -10000,  /**< error was returned from the system level below+                                PortMidi. See ##Pm_GetHostErrorText() */+    pmInvalidDeviceId, /**< Out of range or +                        * output device when input is requested or +                        * input device when output is requested or+                        * device is already opened. +                        */+    pmInsufficientMemory,+    pmBufferTooSmall,+    pmBufferOverflow, /**< buffer overflow (see #Pm_Read) */+    pmBadPtr, /**< #PortMidiStream parameter is NULL or+               * stream is not opened or+               * stream is output when input is required or+               * stream is input when output is required. */+    pmBadData, /**< Illegal midi data, e.g., missing EOX. */+    pmInternalError,+    pmBufferMaxSize, /**< Buffer is already as large as it can be. */+    pmNotImplemented, /**< The function is not implemented, nothing was done. */+    pmInterfaceNotSupported, /**< The requested interface is not supported. */+    pmNameConflict, /**< Cannot create virtual device because name is taken. */+    pmDeviceRemoved  /**< Output attempted after (USB) device was removed. */+    /* NOTE: If you add a new error type, you must update Pm_GetErrorText(). */+} PmError; /**< @brief @enum PmError PortMidi error code; a common return type. +            * No error is indicated by zero; errors are indicated by < 0.+            */++/**+    Pm_Initialize() is the library initialization function - call this before+    using the library.++    *NOTE:* PortMidi scans for available devices when #Pm_Initialize+    is called.  To observe subsequent changes in the available+    devices, you must shut down PortMidi by calling #Pm_Terminate and+    then restart by calling #Pm_Initialize again. *IMPORTANT*: On+    MacOS, #Pm_Initialize *must* always be called on the same+    thread. Otherwise, changes in the available MIDI devices will+    *not* be seen by PortMidi. As an example, if you start PortMidi in+    a thread for processing MIDI, do not try to rescan devices by+    calling #Pm_Initialize in a GUI thread. Instead, start PortMidi+    the first time and every time in the GUI thread.  Alternatively,+    let the GUI request a restart in the MIDI thread.  (These+    restrictions only apply to macOS.) Speaking of threads, on all+    platforms, you are allowed to call #Pm_Initialize in one thread,+    yet send MIDI or poll for incoming MIDI in another+    thread. However, PortMidi is not "thread safe," which means you+    cannot allow threads to call PortMidi functions concurrently.++    @return pmNoError.++    PortMidi is designed to support multiple interfaces (such as ALSA,+    CoreMIDI and WinMM). It is possible to return pmNoError because there+    are no supported interfaces. In that case, zero devices will be +    available.+*/+PMEXPORT PmError Pm_Initialize(void);++/**+    Pm_Terminate() is the library termination function - call this after+    using the library.+*/+PMEXPORT PmError Pm_Terminate(void);++/** Represents an open MIDI device. */+typedef void PortMidiStream;++/** A shorter form of #PortMidiStream. */+#define PmStream PortMidiStream++/** Test whether stream has a pending host error. Normally, the client+    finds out about errors through returned error codes, but some+    errors can occur asynchronously where the client does not+    explicitly call a function, and therefore cannot receive an error+    code.  The client can test for a pending error using+    #Pm_HasHostError(). If true, the error can be accessed by calling+    #Pm_GetHostErrorText().  Pm_Poll() is similar to Pm_HasHostError(),+    but if there is no error, it will return TRUE (1) if there is a+    pending input message.+*/+PMEXPORT int Pm_HasHostError(PortMidiStream * stream);+++/** Translate portmidi error number into human readable message.+    These strings are constants (set at compile time) so client has +    no need to allocate storage.+*/+PMEXPORT const char *Pm_GetErrorText(PmError errnum);++/** Translate portmidi host error into human readable message.+    These strings are computed at run time, so client has to allocate storage.+    After this routine executes, the host error is cleared. +*/+PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len);++/** Any host error msg has at most this many characters, including EOS. */+#define PM_HOST_ERROR_MSG_LEN 256u ++/** Devices are represented as small integers. Device ids range from 0+    to Pm_CountDevices()-1. Pm_GetDeviceInfo() is used to get information+    about the device, and Pm_OpenInput() and PmOpenOutput() are used to+    open the device.+*/+typedef int PmDeviceID;++/** This PmDeviceID (constant) value represents no device and may be+    returned by  Pm_GetDefaultInputDeviceID() or+    Pm_GetDefaultOutputDeviceID() if no default exists.+*/+#define pmNoDevice -1++/** MIDI device information is returned in this structure, which is+    owned by PortMidi and read-only to applications. See Pm_GetDeviceInfo().+*/+typedef struct {+    int structVersion; /**< @brief this internal structure version */ +    const char *interf; /**< @brief underlying MIDI API, e.g. +                                    "MMSystem" or "DirectX" */+    char *name;   /**< @brief device name, e.g. "USB MidiSport 1x1" */+    int input; /**< @brief true iff input is available */+    int output; /**< @brief true iff output is available */+    int opened; /**< @brief used by generic PortMidi for error checking */+    int is_virtual; /**< @brief true iff this is/was a virtual device */+} PmDeviceInfo;+#define PM_DEVICEINFO_VERS 200++/** Version number of PmDeviceInfo, stored in #PmDeviceInfo::structVersion +    field */+#define PM_DEVICEINFO_VERS 200++/** MIDI system-dependent device or driver info is passed in this+    structure, which is owned by the caller.+*/+enum PmSysDepPropertyKey {+    pmKeyNone = 0,  /**< a "noop" key value */+    /** CoreMIDI Manufacturer name, value is string */+    pmKeyCoreMidiManufacturer = 1,+    /** Linux ALSA snd_seq_port_info_set_name, value is a string. Can be +        passed in PmSysDepInfo to Pm_OpenInput or Pm_OpenOutput when opening+        a device. The created port will be named accordingly and will be +        visible for externally made connections (subscriptions). (Linux ALSA+        ports are always enabled for this, but only get application-specific+        names if you give it one.) This key/value is ignored when opening+        virtual ports, which are named when they are created.) */+    pmKeyAlsaPortName = 2,+    /** Linux ALSA snd_seq_set_client_name, value is a string.+        Can be passed in PmSysDepInfo to Pm_OpenInput or Pm_OpenOutput.+        Pm_CreateVirtualInput or Pm_CreateVirtualOutput. Will override+        any previously set client name and applies to all ports. */+    pmKeyAlsaClientName = 3+    /* if system-dependent code introduces more options, register+       the key here to avoid conflicts. */+};+#define PM_SYSDEPINFO_VERS 210++/** System-dependent information can be passed when creating and opening+    ports using this data structure, which stores alternating keys and+    values (addresses). See `pm_test/sendvirtual.c`, `pm_test/recvvirtual.c`,+    and `pm_test/testio.c` for examples.+ */+typedef struct {+    int structVersion;  /**< this structure version */+    int length;  /**< number of properties in this structure */+    struct {+        enum PmSysDepPropertyKey key;+        const void *value;+    } properties[];  /**< array of key/value pairs */+} PmSysDepInfo;++/** Version number of PmSysDepInfo, stored in #PmSysDepInfo::structVersion+    field */+#define PM_SYSDEPINFO_VERS 210++++/** Get devices count, ids range from 0 to Pm_CountDevices()-1. */+PMEXPORT int Pm_CountDevices(void);++/**+    Return the default device ID or pmNoDevice if there are no devices.+    The result (but not pmNoDevice) can be passed to Pm_OpenMidi().+    +    The use of these functions is not recommended. There is no natural+    "default device" on any system, so defaults must be set by users.+    (Currently, PortMidi just returns the first device it finds as+    "default", so if there *is* a default, implementors should use+    pm_add_device to add system default input and output devices+    first.)++    The recommended solution is pass the burden to applications. It is+    easy to scan devices with PortMidi and build a device menu, and to+    save menu selections in application preferences for next+    time. This is my recommendation for any GUI program. For simple+    command-line applications and utilities, see pm_test where all the+    test programs now accept device numbers on the command line and/or+    prompt for their entry.++    On linux, you can create virtual ports and use an external program+    to set up inter-application and device connections.++    Some advice for preferences: MIDI devices used to be built-in or+    plug-in cards, so the numbers rarely changed. Now MIDI devices are+    often plug-in USB devices, so device numbers change, and you+    probably need to design to reinitialize PortMidi to rescan+    devices. MIDI is pretty stateless, so this isn't a big problem,+    although it means you cannot find a new device while playing or+    recording MIDI.++    Since device numbering can change whenever a USB device is plugged+    in, preferences should record *names* of devices rather than+    device numbers. It is simple enough to use string matching to find+    a prefered device, so PortMidi does not provide any built-in+    lookup function.+*/+PMEXPORT PmDeviceID Pm_GetDefaultInputDeviceID(void);++/** @brief see PmDeviceID Pm_GetDefaultInputDeviceID() */+PMEXPORT PmDeviceID Pm_GetDefaultOutputDeviceID(void);++/** Find a device that matches a pattern. ++    @param pattern a substring of the device name, or if the pattern+    contains the two-character separator ", ", then the first part of+    the pattern represents a device interface substring and the second+    part after the separator represents a device name substring. ++    @param is_input restricts the search to an input when true, or an+    output when false.++    @return the number of the first device whose device interface+    contains the interface pattern (if any), whose device name+    contains the name pattern, and whose direction (input or output)+    matches the \p is_input parameter. If no match is found, #pmNoDevice+    (-1) is returned.+*/+PMEXPORT PmDeviceID Pm_FindDevice(char *pattern, int is_input);+++/** Represents a millisecond clock with arbitrary start time. +    This type is used for all MIDI timestamps and clocks.+*/+typedef int32_t PmTimestamp;++/** @brief function pointer to retrieve the time in milliseconds.+    This is the time used in all PortMidi timestamps. The use of+    a function pointer allows the user to derive time from an audio+    sample count to synchronize MIDI to audio, or from a remote+    machine through clock synchronization protocols to synchronize+    MIDI across multiple machine. The time is independent of the+    internal system timestamps (e.g., MacOS CoreMIDI uses its own+    clock and timestamp representation, and PortMidi translates+    between different clocks, which need not be synchronized.) */+typedef PmTimestamp (*PmTimeProcPtr)(void *time_info);++/** TRUE if t1 before t2 */+#define PmBefore(t1,t2) (((t1)-(t2)) < 0)+/** @} */+/** +    \defgroup grp_device Input/Output Devices Handling+    @{+*/+/** Get a PmDeviceInfo structure describing a MIDI device.+    +    @param id the device to be queried.++    If \p id is out of range or if the device designates a deleted+    virtual device, the function returns NULL.++    The returned structure is owned by the PortMidi implementation and+    must not be manipulated or freed. The pointer is guaranteed to be+    valid between calls to Pm_Initialize() and Pm_Terminate().+*/+PMEXPORT const PmDeviceInfo *Pm_GetDeviceInfo(PmDeviceID id);++/** Open a MIDI device for input.++    @param stream the address of a #PortMidiStream pointer which will+    receive a pointer to the newly opened stream.++    @param inputDevice the ID of the device to be opened (see #PmDeviceID).++    @param inputSysDepInfo a pointer to an optional system-dependent+    data structure (a #PmSysDepInfo struct) containing additional+    information for device setup or handle processing. This parameter+    is never required for correct operation. If not used, specify+    NULL.  Declared `void *` here for backward compatibility. Note that+    with Linux ALSA, you can use this parameter to specify a client name+    and port name.++    @param bufferSize the number of input events to be buffered+    waiting to be read using #Pm_Read(). Messages will be lost if the+    number of unread messages exceeds this value.++    @param time_proc (address of) a procedure that returns time in+    milliseconds. It may be NULL, in which case a default millisecond+    timebase (PortTime) is used. If the application wants to use+    PortTime, it should start the timer (call Pt_Start) before calling+    Pm_OpenInput or Pm_OpenOutput. If the application tries to start+    the timer *after* Pm_OpenInput or Pm_OpenOutput, it may get a+    ptAlreadyStarted error from Pt_Start, and the application's+    preferred time resolution and callback function will be ignored.+    \p time_proc result values are appended to incoming MIDI data,+    normally by mapping system-provided timestamps to the \p time_proc+    timestamps to maintain the precision of system-provided+    timestamps.++    @param time_info is a pointer passed to time_proc.++    @return #pmNoError and places a pointer to a valid+    #PortMidiStream in the stream argument.  If the open operation+    fails, a nonzero error code is returned (see #PmError) and+    the value of stream is invalid.++    Any stream that is successfully opened should eventually be closed+    by calling Pm_Close().+*/+PMEXPORT PmError Pm_OpenInput(PortMidiStream** stream,+                PmDeviceID inputDevice,+                void *inputSysDepInfo,+                int32_t bufferSize,+                PmTimeProcPtr time_proc,+                void *time_info);++/** Open a MIDI device for output.++    @param stream the address of a #PortMidiStream pointer which will+    receive a pointer to the newly opened stream.++    @param outputDevice the ID of the device to be opened (see #PmDeviceID).++    @param outputSysDepInfo a pointer to an optional system-specific+    data structure (a #PmSysDepInfo struct) containing additional+    information for device setup or handle processing. This parameter+    is never required for correct operation. If not used, specify+    NULL. Declared `void *` here for backward compatibility. Note that+    with Linux ALSA, you can use this parameter to specify a client name+    and port name.++    @param bufferSize the number of output events to be buffered+    waiting for output. In some cases -- see below -- PortMidi does+    not buffer output at all and merely passes data to a lower-level+    API, in which case \p bufferSize is ignored. Since MIDI speeds now+    vary from 1 to 50 or more messages per ms (over USB), put some+    thought into this number. E.g. if latency is 20ms and you want to+    burst 100 messages in that time (5000 messages per second), you+    should set \p bufferSize to at least 100. The default on Windows+    assumes an average rate of 500 messages per second and in this+    example, output would be slowed waiting for free buffers.+    +    @param latency the delay in milliseconds applied to timestamps+    to determine when the output should actually occur. (If latency is+    < 0, 0 is assumed.)  If latency is zero, timestamps are ignored+    and all output is delivered immediately. If latency is greater+    than zero, output is delayed until the message timestamp plus the+    latency. (NOTE: the time is measured relative to the time source+    indicated by time_proc. Timestamps are absolute, not relative+    delays or offsets.) In some cases, PortMidi can obtain better+    timing than your application by passing timestamps along to the+    device driver or hardware, so the best strategy to minimize jitter+    is: wait until the real time to send the message, compute the+    message, attach the *ideal* output time (not the current real+    time, because some time may have elapsed), and send the+    message. The \p latency will be added to the timestamp, and+    provided the elapsed computation time has not exceeded \p latency,+    the message will be delivered according to the timestamp. If the+    real time is already past the timestamp, the message will be+    delivered as soon as possible. Latency may also help you to+    synchronize MIDI data to audio data by matching \p latency to the+    audio buffer latency.++    @param time_proc (address of) a pointer to a procedure that+    returns time in milliseconds. It may be NULL, in which case a+    default millisecond timebase (PortTime) is used. If the+    application wants to use PortTime, it should start the timer (call+    Pt_Start) before calling #Pm_OpenInput or #Pm_OpenOutput. If the+    application tries to start the timer *after* #Pm_OpenInput or+    #Pm_OpenOutput, it may get a #ptAlreadyStarted error from #Pt_Start,+    and the application's preferred time resolution and callback+    function will be ignored.  \p time_proc times are used to schedule+    outgoing MIDI data (when latency is non-zero), usually by mapping+    from time_proc timestamps to internal system timestamps to+    maintain the precision of system-supported timing.++    @param time_info a pointer passed to time_proc.++    @return #pmNoError and places a pointer to a valid #PortMidiStream+    in the stream argument.  If the operation fails, a nonzero error+    code is returned (see PMError) and the value of \p stream is+    invalid.++    Note: ALSA appears to have a fixed-size priority queue for timed+    output messages. Testing indicates the queue can hold a little+    over 400 3-byte MIDI messages. Thus, you can send 10,000+    messages/second if the latency is 30ms (30ms * 10000 msgs/sec *+    0.001 sec/ms = 300 msgs), but not if the latency is 50ms+    (resulting in about 500 pending messages, which is greater than+    the 400 message limit). Since timestamps in ALSA are relative,+    they are of less value than absolute timestamps in macOS and+    Windows. This is a limitation of ALSA and apparently a design+    flaw.++    Example 1: If I provide a timestamp of 5000, latency is 1, and+    time_proc returns 4990, then the desired output time will be when+    time_proc returns timestamp+latency = 5001. This will be 5001-4990+    = 11ms from now.++    Example 2: If I want to send at exactly 5010, and latency is 10, I+    should wait until 5000, compute the messages and provide a+    timestamp of 5000. As long as computation takes less than 10ms,+    the message will be delivered at time 5010.++    Example 3 (recommended): It is often convenient to ignore latency.+    E.g. if a sequence says to output at time 5010, just wait until+    5010, compute the message and use 5010 for the timestamp. Delivery+    will then be at 5010+latency, but unless you are synchronizing to+    something else, the absolute delay by latency will not matter.++    Any stream that is successfully opened should eventually be closed+    by calling Pm_Close().+*/+PMEXPORT PmError Pm_OpenOutput(PortMidiStream** stream,+                PmDeviceID outputDevice,+                void *outputSysDepInfo,+                int32_t bufferSize,+                PmTimeProcPtr time_proc,+                void *time_info,+                int32_t latency);++/** Create  a virtual input device.++    @param name gives the virtual device name, which is visible to+    other applications.++    @param interf is the interface (System API) used to create the+    device Default interfaces are "MMSystem", "CoreMIDI" and+    "ALSA". Currently, these are the only ones implemented, but future+    implementations could support DirectMusic, Jack, sndio, or others.++    @param sysDepInfo contains interface-dependent additional+    information (a #PmSysDepInfo struct), e.g., hints or options. This+    parameter is never required for correct operation. If not used,+    specify NULL. Declared `void *` here for backward compatibility.++    @return a device ID or #pmNameConflict (\p name is invalid or+    already exists) or #pmInterfaceNotSupported (\p interf is does not+    match a supported interface).++    The created virtual device appears to other applications as if it+    is an output device. The device must be opened to obtain a stream+    and read from it.++    Virtual devices are not supported by Windows (Multimedia API). Calls+    on Windows do nothing except return #pmNotImplemented.+*/+PMEXPORT PmError Pm_CreateVirtualInput(const char *name,+                                       const char *interf,+                                       void *sysDepInfo);++/** Create a virtual output device.++    @param name gives the virtual device name, which is visible to+    other applications.++    @param interf is the interface (System API) used to create the+    device Default interfaces are "MMSystem", "CoreMIDI" and+    "ALSA". Currently, these are the only ones implemented, but future+    implementations could support DirectMusic, Jack, sndio, or others.++    @param sysDepInfo contains interface-dependent additional+    information (a #PmSysDepInfo struct), e.g., hints or options. This+    parameter is never required for correct operation. If not used,+    specify NULL. Declared `void *` here for backward compatibility.++    @return a device ID or #pmInvalidDeviceId (\p name is invalid or+    already exists) or #pmInterfaceNotSupported (\p interf is does not+    match a supported interface).++    The created virtual device appears to other applications as if it+    is an input device. The device must be opened to obtain a stream+    and write to it.++    Virtual devices are not supported by Windows (Multimedia API). Calls+    on Windows do nothing except return #pmNotImplemented.+*/+PMEXPORT PmError Pm_CreateVirtualOutput(const char *name,+                                        const char *interf,+                                        void *sysDepInfo);++/** Remove a virtual device.++   @param device a device ID (small integer) designating the device.++   The device is removed; other applications can no longer see or open+   this virtual device, which may be either for input or output. The+   device must not be open. The device ID may be reused, but existing+   devices are not renumbered. This means that the device ID could be+   in the range from 0 to #Pm_CountDevices(), yet the device ID does+   not designate a device. In that case, passing the ID to+   #Pm_GetDeviceInfo() will return NULL.++   @return #pmNoError if the device was deleted or #pmInvalidDeviceId+   if the device is open, already deleted, or \p device is out of+   range.+*/+PMEXPORT PmError Pm_DeleteVirtualDevice(PmDeviceID device);+  /** @} */++/**+   @defgroup grp_events_filters Events and Filters Handling+   @{+*/++/* Filter bit-mask definitions */+/** filter active sensing messages (0xFE): */+#define PM_FILT_ACTIVE (1 << 0x0E)+/** filter system exclusive messages (0xF0): */+#define PM_FILT_SYSEX (1 << 0x00)+/** filter MIDI clock message (0xF8) */+#define PM_FILT_CLOCK (1 << 0x08)+/** filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */+#define PM_FILT_PLAY ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B))+/** filter tick messages (0xF9) */+#define PM_FILT_TICK (1 << 0x09)+/** filter undefined FD messages */+#define PM_FILT_FD (1 << 0x0D)+/** filter undefined real-time messages */+#define PM_FILT_UNDEFINED PM_FILT_FD+/** filter reset messages (0xFF) */+#define PM_FILT_RESET (1 << 0x0F)+/** filter all real-time messages */+#define PM_FILT_REALTIME (PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK | \+    PM_FILT_PLAY | PM_FILT_UNDEFINED | PM_FILT_RESET | PM_FILT_TICK)+/** filter note-on and note-off (0x90-0x9F and 0x80-0x8F */+#define PM_FILT_NOTE ((1 << 0x19) | (1 << 0x18))+/** filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/+#define PM_FILT_CHANNEL_AFTERTOUCH (1 << 0x1D)+/** per-note aftertouch (0xA0-0xAF) */+#define PM_FILT_POLY_AFTERTOUCH (1 << 0x1A)+/** filter both channel and poly aftertouch */+#define PM_FILT_AFTERTOUCH (PM_FILT_CHANNEL_AFTERTOUCH | \+                            PM_FILT_POLY_AFTERTOUCH)+/** Program changes (0xC0-0xCF) */+#define PM_FILT_PROGRAM (1 << 0x1C)+/** Control Changes (CC's) (0xB0-0xBF)*/+#define PM_FILT_CONTROL (1 << 0x1B)+/** Pitch Bender (0xE0-0xEF*/+#define PM_FILT_PITCHBEND (1 << 0x1E)+/** MIDI Time Code (0xF1)*/+#define PM_FILT_MTC (1 << 0x01)+/** Song Position (0xF2) */+#define PM_FILT_SONG_POSITION (1 << 0x02)+/** Song Select (0xF3)*/+#define PM_FILT_SONG_SELECT (1 << 0x03)+/** Tuning request (0xF6) */+#define PM_FILT_TUNE (1 << 0x06)+/** All System Common messages (mtc, song position, song select, tune request) */+#define PM_FILT_SYSTEMCOMMON (PM_FILT_MTC | PM_FILT_SONG_POSITION | \+                              PM_FILT_SONG_SELECT | PM_FILT_TUNE)+++/** Set filters on an open input stream to drop selected input types.+    +    @param stream an open MIDI input stream.++    @param filters indicate message types to filter (block).++    @return #pmNoError or an error code.++    By default, only active sensing messages are filtered.+    To prohibit, say, active sensing and sysex messages, call+    Pm_SetFilter(stream, PM_FILT_ACTIVE | PM_FILT_SYSEX);++    Filtering is useful when midi routing or midi thru functionality+    is being provided by the user application.+    For example, you may want to exclude timing messages (clock, MTC,+    start/stop/continue), while allowing note-related messages to pass.+    Or you may be using a sequencer or drum-machine for MIDI clock+    information but want to exclude any notes it may play.+ */+PMEXPORT PmError Pm_SetFilter(PortMidiStream* stream, int32_t filters);++/** Create a mask that filters one channel. */+#define Pm_Channel(channel) (1<<(channel))++/** Filter incoming messages based on channel.++    @param stream an open MIDI input stream.++    @param mask indicates channels to be received.++    @return #pmNoError or an error code.++    The \p mask is a 16-bit bitfield corresponding to appropriate channels.+    The #Pm_Channel macro can assist in calling this function.+    I.e. to receive only input on channel 1, call with+    Pm_SetChannelMask(Pm_Channel(1));+    Multiple channels should be OR'd together, like+    Pm_SetChannelMask(Pm_Channel(10) | Pm_Channel(11))++    Note that channels are numbered 0 to 15 (not 1 to 16). Most +    synthesizer and interfaces number channels starting at 1, but+    PortMidi numbers channels starting at 0.++    All channels are allowed by default+*/+PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask);++/** Terminate outgoing messages immediately.++    @param stream an open MIDI output stream.++    @result #pmNoError or an error code.++    The caller should immediately close the output port; this call may+    result in transmission of a partial MIDI message.  There is no+    abort for Midi input because the user can simply ignore messages+    in the buffer and close an input device at any time. If the+    specified behavior cannot be achieved through the system-level+    interface (ALSA, CoreMIDI, etc.), the behavior may be that of +    Pm_Close().+ */+PMEXPORT PmError Pm_Abort(PortMidiStream* stream);+     +/** Close a midi stream, flush any pending buffers if possible.++    @param stream an open MIDI input or output stream.++    @result #pmNoError or an error code.++    If the system-level interface (ALSA, CoreMIDI, etc.) does not+    support flushing remaining messages, the behavior may be one of+    the following (most preferred first): block until all pending+    timestamped messages are delivered; deliver messages to a server+    or kernel process for later delivery but return immediately; drop+    messages (as in Pm_Abort()).  Therefore, to be safe, applications+    should wait until the output queue is empty before calling+    Pm_Close(). E.g. calling Pt_Sleep(100 + latency); will give a+    100ms "cushion" beyond latency (if any) before closing.+*/+PMEXPORT PmError Pm_Close(PortMidiStream* stream);++/** (re)synchronize to the time_proc passed when the stream was opened.++    @param stream an open MIDI input or output stream.++    @result #pmNoError or an error code.++    Typically, this is used when the stream must be opened before the+    time_proc reference is actually advancing. In this case, message+    timing may be erratic, but since timestamps of zero mean "send+    immediately," initialization messages with zero timestamps can be+    written without a functioning time reference and without+    problems. Before the first MIDI message with a non-zero timestamp+    is written to the stream, the time reference must begin to advance+    (for example, if the time_proc computes time based on audio+    samples, time might begin to advance when an audio stream becomes+    active). After time_proc return values become valid, and BEFORE+    writing the first non-zero timestamped MIDI message, call+    Pm_Synchronize() so that PortMidi can observe the difference+    between the current time_proc value and its MIDI stream time.+    +    In the more normal case where time_proc values advance+    continuously, there is no need to call #Pm_Synchronize. PortMidi+    will always synchronize at the first output message and+    periodically thereafter.+*/+PMEXPORT PmError Pm_Synchronize(PortMidiStream* stream);+++/** Encode a short Midi message into a 32-bit word. If data1+    and/or data2 are not present, use zero.+*/+#define Pm_Message(status, data1, data2) \+         ((((data2) << 16) & 0xFF0000) | \+          (((data1) << 8) & 0xFF00) | \+          ((status) & 0xFF))+/** Extract the status field from a 32-bit midi message. */+#define Pm_MessageStatus(msg) ((msg) & 0xFF)+/** Extract the 1st data field (e.g., pitch) from a 32-bit midi message. */+#define Pm_MessageData1(msg) (((msg) >> 8) & 0xFF)+/** Extract the 2nd data field (e.g., velocity) from a 32-bit midi message. */+#define Pm_MessageData2(msg) (((msg) >> 16) & 0xFF)++typedef uint32_t PmMessage; /**< @brief see #PmEvent */+/**+   All MIDI data comes in the form of PmEvent structures. A sysex+   message is encoded as a sequence of PmEvent structures, with each+   structure carrying 4 bytes of the message, i.e. only the first+   PmEvent carries the status byte.++   All other MIDI messages take 1 to 3 bytes and are encoded in a whole+   PmMessage with status in the low-order byte and remaining bytes +   unused, i.e., a 3-byte note-on message will occupy 3 low-order bytes+   of PmMessage, leaving the high-order byte unused.++   Note that MIDI allows nested messages: the so-called "real-time" MIDI +   messages can be inserted into the MIDI byte stream at any location, +   including within a sysex message. MIDI real-time messages are one-byte+   messages used mainly for timing (see the MIDI spec). PortMidi retains +   the order of non-real-time MIDI messages on both input and output, but +   it does not specify exactly how real-time messages are processed. This+   is particulary problematic for MIDI input, because the input parser +   must either prepare to buffer an unlimited number of sysex message +   bytes or to buffer an unlimited number of real-time messages that +   arrive embedded in a long sysex message. To simplify things, the input+   parser is allowed to pass real-time MIDI messages embedded within a +   sysex message, and it is up to the client to detect, process, and +   remove these messages as they arrive.++   When receiving sysex messages, the sysex message is terminated+   by either an EOX status byte (anywhere in the 4 byte messages) or+   by a non-real-time status byte in the low order byte of the message.+   If you get a non-real-time status byte but there was no EOX byte, it +   means the sysex message was somehow truncated. This is not+   considered an error; e.g., a missing EOX can result from the user+   disconnecting a MIDI cable during sysex transmission.++   A real-time message can occur within a sysex message. A real-time +   message will always occupy a full PmEvent with the status byte in +   the low-order byte of the PmEvent message field. (This implies that+   the byte-order of sysex bytes and real-time message bytes may not+   be preserved -- for example, if a real-time message arrives after+   3 bytes of a sysex message, the real-time message will be delivered+   first. The first word of the sysex message will be delivered only+   after the 4th byte arrives, filling the 4-byte PmEvent message field.+   +   The timestamp field is observed when the output port is opened with+   a non-zero latency. A timestamp of zero means "use the current time",+   which in turn means to deliver the message with a delay of+   latency (the latency parameter used when opening the output port.)+   Do not expect PortMidi to sort data according to timestamps -- +   messages should be sent in the correct order, and timestamps MUST +   be non-decreasing. See also "Example" for Pm_OpenOutput() above.++   A sysex message will generally fill many #PmEvent structures. On +   output to a #PortMidiStream with non-zero latency, the first timestamp+   on sysex message data will determine the time to begin sending the +   message. PortMidi implementations may ignore timestamps for the +   remainder of the sysex message. +   +   On input, the timestamp ideally denotes the arrival time of the +   status byte of the message. The first timestamp on sysex message +   data will be valid. Subsequent timestamps may denote +   when message bytes were actually received, or they may be simply +   copies of the first timestamp.++   Timestamps for nested messages: If a real-time message arrives in +   the middle of some other message, it is enqueued immediately with +   the timestamp corresponding to its arrival time. The interrupted +   non-real-time message or 4-byte packet of sysex data will be enqueued +   later. The timestamp of interrupted data will be equal to that of+   the interrupting real-time message to insure that timestamps are+   non-decreasing.+ */+typedef struct {+    PmMessage      message;    /**< up to 4 bytes of MIDI data */+    PmTimestamp    timestamp;  /**< PortMidi time of message */+} PmEvent;++/** @} */++/** \defgroup grp_io Reading and Writing Midi Messages+    @{+*/+/** Retrieve midi data into a buffer. ++    @param stream the open input stream.++    @param buffer input data is stored here++    @param length the length of buffer (number of #PmEvent, not bytes)++    @return the number of events read, or, if the result is negative,+    a #PmError value will be returned.++    The Buffer Overflow Problem++    The problem: if an input overflow occurs, data will be lost,+    ultimately because there is no flow control all the way back to+    the data source.  When data is lost, the receiver should be+    notified and some sort of graceful recovery should take place,+    e.g. you shouldn't resume receiving in the middle of a long sysex+    message.++    With a lock-free fifo, which is pretty much what we're stuck with+    to enable portability to the Mac, it's tricky for the producer and+    consumer to synchronously reset the buffer and resume normal+    operation.++    Solution: the entire buffer managed by PortMidi will be flushed+    when an overflow occurs. The consumer (#Pm_Read()) gets an error+    message (#pmBufferOverflow) and ordinary processing resumes as+    soon as a new message arrives. The remainder of a partial sysex+    message is not considered to be a "new message" and will be+    flushed as well.+*/+PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length);++/** Test whether input is available.++    @param stream an open input stream.++    @return TRUE, FALSE, or an error value. ++    If there was an asynchronous error, pmHostError is returned and you must+    call again to determine if input is (also) available.++    You should probably *not* use this function. Call Pm_Read()+    instead. If it returns 0, then there is no data available. It is+    possible for Pm_Poll() to return TRUE before the complete message+    is available, so Pm_Read() could return 0 even after Pm_Poll()+    returns TRUE. Only call Pm_Poll() if you want to know that data is+    probably available even though you are not ready to receive data.+*/+PMEXPORT PmError Pm_Poll(PortMidiStream *stream);++/** Write MIDI data from a buffer. ++    @param stream an open output stream.++    @param buffer (address of) an array of MIDI event data.++    @param length the length of the \p buffer.++    @return #pmNoError, #pmBadPtr (if \p stream is not valid and opened),+        #pmDeviceRemoved (if the MIDI device no longer exists),+        #pmBadData (if \p buffer data does not represent valid MIDI, e.g.,+        nested SYSEX messages, or #pmHostError (error returned from API's+        MIDI write operation, see #Pm_GetHostErrorText). ++    \b buffer may contain:+        - short messages +        - sysex messages that are converted into a sequence of PmEvent+          structures, e.g. sending data from a file or forwarding them+          from midi input, with 4 SysEx bytes per PmEvent message,+          low-order byte first, until the last message, which may+          contain from 1 to 4 bytes ending in MIDI EOX (0xF7).+        - PortMidi allows 1-byte real-time messages to be embedded+          within SysEx messages, but only on 4-byte boundaries so+          that SysEx data always uses a full 4 bytes (except possibly+          at the end). Each real-time message always occupies a full+          PmEvent (3 of the 4 bytes in the PmEvent's message are+          ignored) even when embedded in a SysEx message.++    Use Pm_WriteSysEx() to write a sysex message stored as a contiguous +    array of bytes.++    Sysex data may contain embedded real-time messages.++    \p buffer is managed by the caller. The buffer may be destroyed+    as soon as this call returns.+*/+PMEXPORT PmError Pm_Write(PortMidiStream *stream, PmEvent *buffer,+                          int32_t length);++/** Write a timestamped non-system-exclusive midi message.++    @param stream an open output stream.++    @param when timestamp for the event.++    @param msg the data for the event.++    @return #pmNoError, #pmBadPtr (if \p stream is not valid and opened),+        #pmDeviceRemoved (if the MIDI device no longer exists),+        #pmBadData (if \p buffer data does not represent valid MIDI, e.g.,+        nested SYSEX messages, or #pmHostError (error returned from API's+        MIDI write operation, see #Pm_GetHostErrorText). ++    Messages are delivered in order, and timestamps must be+    non-decreasing. (But timestamps are ignored if the stream was+    opened with latency = 0, and otherwise, non-decreasing timestamps+    are "corrected" to the lowest valid value.)+*/+PMEXPORT PmError Pm_WriteShort(PortMidiStream *stream, PmTimestamp when,+                               PmMessage msg);++/** Write a timestamped system-exclusive midi message.++    @param stream an open output stream.++    @param when timestamp for the event.++    @param msg the sysex message, terminated with an EOX status byte.++    @return #pmNoError, #pmBadPtr (if \p stream is not valid and opened),+        #pmDeviceRemoved (if the MIDI device no longer exists),+        #pmBadData (if \p buffer data does not represent valid MIDI, e.g.,+        nested SYSEX messages, or #pmHostError (error returned from API's+        MIDI write operation, see #Pm_GetHostErrorText). ++    \p msg is managed by the caller and may be destroyed when this+    call returns.+*/+PMEXPORT PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when, +                               unsigned char *msg);++/** @} */++#ifdef __cplusplus+}+#endif /* __cplusplus */++#endif /* PORTMIDI_PORTMIDI_H */
portmidi/pm_linux/pmlinux.c view
@@ -1,60 +1,68 @@-/* pmlinux.c -- PortMidi os-dependent code */
-
-/* This file only needs to implement pm_init(), which calls various
-   routines to register the available midi devices. This file must
-   be separate from the main portmidi.c file because it is system
-   dependent, and it is separate from, pmlinuxalsa.c, because it
-   might need to register non-alsa devices as well.
-
-   NOTE: if you add non-ALSA support, you need to fix :alsa_poll()
-   in pmlinuxalsa.c, which assumes all input devices are ALSA.
- */
-
-#include "stdlib.h"
-#include "portmidi.h"
-#ifdef PMALSA
-  #include "pmlinuxalsa.h"
-#endif
-
-#ifdef PMNULL
-  #include "pmlinuxnull.h"
-#endif
-
-PmError pm_init()
-{
-    /* Note: it is not an error for PMALSA to fail to initialize. 
-     * It may be a design error that the client cannot query what subsystems
-     * are working properly other than by looking at the list of available
-     * devices.
-     */
-    #ifdef PMALSA
-	pm_linuxalsa_init();
-    #endif
-    #ifdef PMNULL
-        pm_linuxnull_init();
-    #endif
-    return pmNoError;
-}
-
-void pm_term(void)
-{
-    #ifdef PMALSA
-        pm_linuxalsa_term();
-    #endif
-}
-
-PmDeviceID pm_default_input_device_id = -1;
-PmDeviceID pm_default_output_device_id = -1;
-
-PmDeviceID Pm_GetDefaultInputDeviceID() { 
-    return pm_default_input_device_id; 
-}
-
-PmDeviceID Pm_GetDefaultOutputDeviceID() { 
-    return pm_default_output_device_id; 
-}
-
-void *pm_alloc(size_t s) { return malloc(s); }
-
-void pm_free(void *ptr) { free(ptr); }
-
+/* pmlinux.c -- PortMidi os-dependent code */++/* This file only needs to implement pm_init(), which calls various+   routines to register the available midi devices. This file must+   be separate from the main portmidi.c file because it is system+   dependent, and it is separate from, pmlinuxalsa.c, because it+   might need to register non-alsa devices as well.++   NOTE: if you add non-ALSA support, you need to fix :alsa_poll()+   in pmlinuxalsa.c, which assumes all input devices are ALSA.+ */++#include "stdlib.h"+#include "portmidi.h"+#include "pmutil.h"+#include "pminternal.h"++#ifdef PMALSA+  #include "pmlinuxalsa.h"+#endif++#ifdef PMNULL+  #include "pmlinuxnull.h"+#endif++#if !(defined(PMALSA) || defined(PMNULL))+#error One of PMALSA or PMNULL must be defined+#endif++void pm_init(void)+{+    /* Note: it is not an error for PMALSA to fail to initialize. +     * It may be a design error that the client cannot query what subsystems+     * are working properly other than by looking at the list of available+     * devices.+     */+#ifdef PMALSA+    pm_linuxalsa_init();+#endif+#ifdef PMNULL+        pm_linuxnull_init();+#endif+}++void pm_term(void)+{+    #ifdef PMALSA+        pm_linuxalsa_term();+    #endif+    #ifdef PMNULL+        pm_linuxnull_term();+    #endif+}++PmDeviceID Pm_GetDefaultInputDeviceID(void) { +    Pm_Initialize();+    return pm_default_input_device_id; +}++PmDeviceID Pm_GetDefaultOutputDeviceID(void) { +    Pm_Initialize();+    return pm_default_output_device_id; +}++void *pm_alloc(size_t s) { return malloc(s); }++void pm_free(void *ptr) { free(ptr); }+
− portmidi/pm_linux/pmlinux.h
@@ -1,5 +0,0 @@-/* pmlinux.h */
-
-extern PmDeviceID pm_default_input_device_id;
-extern PmDeviceID pm_default_output_device_id;
-
portmidi/pm_linux/pmlinuxalsa.c view
@@ -1,789 +1,913 @@-/*
- * pmlinuxalsa.c -- system specific definitions
- * 
- * written by:
- *  Roger Dannenberg (port to Alsa 0.9.x)
- *  Clemens Ladisch (provided code examples and invaluable consulting)
- *  Jason Cohen, Rico Colon, Matt Filippone (Alsa 0.5.x implementation)
- */ 
-
-#include "stdlib.h"
-#include "portmidi.h"
-#include "pmutil.h"
-#include "pminternal.h"
-#include "pmlinuxalsa.h"
-#include "string.h"
-#include "porttime.h"
-#include "pmlinux.h"
-
-#include <alsa/asoundlib.h>
-
-/* I used many print statements to debug this code. I left them in the
- * source, and you can turn them on by changing false to true below:
- */
-#define VERBOSE_ON 0
-#define VERBOSE if (VERBOSE_ON)
-
-#define MIDI_SYSEX      0xf0
-#define MIDI_EOX        0xf7
-
-#if SND_LIB_MAJOR == 0 && SND_LIB_MINOR < 9
-#error needs ALSA 0.9.0 or later
-#endif
-
-/* to store client/port in the device descriptor */
-#define MAKE_DESCRIPTOR(client, port) ((void*)(((client) << 8) | (port)))
-#define GET_DESCRIPTOR_CLIENT(info) ((((int)(info)) >> 8) & 0xff)
-#define GET_DESCRIPTOR_PORT(info) (((int)(info)) & 0xff)
-
-#define BYTE unsigned char
-#define UINT unsigned long
-
-extern pm_fns_node pm_linuxalsa_in_dictionary;
-extern pm_fns_node pm_linuxalsa_out_dictionary;
-
-static snd_seq_t *seq = NULL; // all input comes here, 
-                              // output queue allocated on seq
-static int queue, queue_used; /* one for all ports, reference counted */
-
-typedef struct alsa_descriptor_struct {
-    int client;
-    int port;
-    int this_port;
-    int in_sysex;
-    snd_midi_event_t *parser;
-    int error; /* host error code */
-} alsa_descriptor_node, *alsa_descriptor_type;
-
-
-/* get_alsa_error_text -- copy error text to potentially short string */
-/**/
-static void get_alsa_error_text(char *msg, int len, int err)
-{
-    int errlen = strlen(snd_strerror(err));
-    if (errlen < len) {
-        strcpy(msg, snd_strerror(err));
-    } else if (len > 20) {
-        sprintf(msg, "Alsa error %d", err);
-    } else if (len > 4) {
-        strcpy(msg, "Alsa");
-    } else {
-        msg[0] = 0;
-    }
-}
-
-
-/* queue is shared by both input and output, reference counted */
-static PmError alsa_use_queue(void)
-{
-    if (queue_used == 0) {
-        snd_seq_queue_tempo_t *tempo;
-
-        queue = snd_seq_alloc_queue(seq);
-        if (queue < 0) {
-            pm_hosterror = queue;
-            return pmHostError;
-        }
-        snd_seq_queue_tempo_alloca(&tempo);
-        snd_seq_queue_tempo_set_tempo(tempo, 480000);
-        snd_seq_queue_tempo_set_ppq(tempo, 480);
-        pm_hosterror = snd_seq_set_queue_tempo(seq, queue, tempo);
-        if (pm_hosterror < 0)
-            return pmHostError;
-
-        snd_seq_start_queue(seq, queue, NULL);
-        snd_seq_drain_output(seq);
-    }
-    ++queue_used;
-    return pmNoError;
-}
-
-
-static void alsa_unuse_queue(void)
-{
-    if (--queue_used == 0) {
-        snd_seq_stop_queue(seq, queue, NULL);
-        snd_seq_drain_output(seq);
-        snd_seq_free_queue(seq, queue);
-        VERBOSE printf("queue freed\n");
-    }
-}
-
-
-/* midi_message_length -- how many bytes in a message? */
-static int midi_message_length(PmMessage message)
-{
-    message &= 0xff;
-    if (message < 0x80) {
-        return 0;
-    } else if (message < 0xf0) {
-        static const int length[] = {3, 3, 3, 3, 2, 2, 3};
-        return length[(message - 0x80) >> 4];
-    } else {
-        static const int length[] = {
-            -1, 2, 3, 2, 0, 0, 1, -1, 1, 0, 1, 1, 1, 0, 1, 1};
-        return length[message - 0xf0];
-    }
-}
-
-
-static PmError alsa_out_open(PmInternal *midi, void *driverInfo) 
-{
-    void *client_port = descriptors[midi->device_id].descriptor;
-    alsa_descriptor_type desc = (alsa_descriptor_type) 
-        pm_alloc(sizeof(alsa_descriptor_node));
-    snd_seq_port_info_t *info;
-    int err;
-
-    if (!desc) return pmInsufficientMemory;
-    
-    snd_seq_port_info_alloca(&info);
-    snd_seq_port_info_set_port(info, midi->device_id);
-    snd_seq_port_info_set_capability(info, SND_SEQ_PORT_CAP_WRITE |
-                                     SND_SEQ_PORT_CAP_READ);
-    snd_seq_port_info_set_type(info, SND_SEQ_PORT_TYPE_MIDI_GENERIC | 
-                                     SND_SEQ_PORT_TYPE_APPLICATION);
-    snd_seq_port_info_set_port_specified(info, 1);
-    err = snd_seq_create_port(seq, info);
-    if (err < 0) goto free_desc;
-
-    /* fill in fields of desc, which is passed to pm_write routines */
-    midi->descriptor = desc;
-    desc->client = GET_DESCRIPTOR_CLIENT(client_port);
-    desc->port = GET_DESCRIPTOR_PORT(client_port);
-    desc->this_port = midi->device_id;
-    desc->in_sysex = 0;
-
-    desc->error = 0;
-
-    err = snd_midi_event_new(PM_DEFAULT_SYSEX_BUFFER_SIZE, &desc->parser);
-    if (err < 0) goto free_this_port;
-
-    if (midi->latency > 0) { /* must delay output using a queue */
-        err = alsa_use_queue();
-        if (err < 0) goto free_parser;
-
-        err = snd_seq_connect_to(seq, desc->this_port, desc->client, desc->port);
-        if (err < 0) goto unuse_queue;  /* clean up and return on error */
-    } else {
-        err = snd_seq_connect_to(seq, desc->this_port, desc->client, desc->port);
-        if (err < 0) goto free_parser;  /* clean up and return on error */
-    }        
-    return pmNoError;
-
- unuse_queue:
-    alsa_unuse_queue();
- free_parser:
-    snd_midi_event_free(desc->parser);
- free_this_port:
-    snd_seq_delete_port(seq, desc->this_port);
- free_desc:
-    pm_free(desc);
-    pm_hosterror = err;
-    if (err < 0) {
-        get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err);
-    }
-    return pmHostError;
-}
-    
-
-static PmError alsa_write_byte(PmInternal *midi, unsigned char byte, 
-                        PmTimestamp timestamp)
-{
-    alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
-    snd_seq_event_t ev;
-    int err;
-
-    snd_seq_ev_clear(&ev);
-    if (snd_midi_event_encode_byte(desc->parser, byte, &ev) == 1) {
-        snd_seq_ev_set_dest(&ev, desc->client, desc->port);
-        snd_seq_ev_set_source(&ev, desc->this_port);
-        if (midi->latency > 0) {
-            /* compute relative time of event = timestamp - now + latency */
-            PmTimestamp now = (midi->time_proc ? 
-                               midi->time_proc(midi->time_info) : 
-                               Pt_Time(NULL));
-            int when = timestamp;
-            /* if timestamp is zero, send immediately */
-            /* otherwise compute time delay and use delay if positive */
-            if (when == 0) when = now;
-            when = (when - now) + midi->latency;
-            if (when < 0) when = 0;
-            VERBOSE printf("timestamp %d now %d latency %d, ", 
-                           (int) timestamp, (int) now, midi->latency);
-            VERBOSE printf("scheduling event after %d\n", when);
-            /* message is sent in relative ticks, where 1 tick = 1 ms */
-            snd_seq_ev_schedule_tick(&ev, queue, 1, when);
-            /* NOTE: for cases where the user does not supply a time function,
-               we could optimize the code by not starting Pt_Time and using
-               the alsa tick time instead. I didn't do this because it would
-               entail changing the queue management to start the queue tick
-               count when PortMidi is initialized and keep it running until
-               PortMidi is terminated. (This should be simple, but it's not
-               how the code works now.) -RBD */
-        } else { /* send event out without queueing */
-            VERBOSE printf("direct\n");
-            /* ev.queue = SND_SEQ_QUEUE_DIRECT;
-               ev.dest.client = SND_SEQ_ADDRESS_SUBSCRIBERS; */
-            snd_seq_ev_set_direct(&ev);
-        }
-        VERBOSE printf("sending event\n");
-        err = snd_seq_event_output(seq, &ev);
-        if (err < 0) {
-            desc->error = err;
-            return pmHostError;
-        }
-    }
-    return pmNoError;
-}
-
-
-static PmError alsa_out_close(PmInternal *midi)
-{
-    alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
-    if (!desc) return pmBadPtr;
-
-    if (pm_hosterror = snd_seq_disconnect_to(seq, desc->this_port, 
-                                             desc->client, desc->port)) {
-        // if there's an error, try to delete the port anyway, but don't
-        // change the pm_hosterror value so we retain the first error
-        snd_seq_delete_port(seq, desc->this_port);
-    } else { // if there's no error, delete the port and retain any error
-        pm_hosterror = snd_seq_delete_port(seq, desc->this_port);
-    }
-    if (midi->latency > 0) alsa_unuse_queue();
-    snd_midi_event_free(desc->parser);
-    midi->descriptor = NULL; /* destroy the pointer to signify "closed" */
-    pm_free(desc);
-    if (pm_hosterror) {
-        get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, 
-                            pm_hosterror);
-        return pmHostError;
-    }
-    return pmNoError;
-}
-
-
-static PmError alsa_in_open(PmInternal *midi, void *driverInfo)
-{
-    void *client_port = descriptors[midi->device_id].descriptor;
-    alsa_descriptor_type desc = (alsa_descriptor_type) 
-        pm_alloc(sizeof(alsa_descriptor_node));
-    snd_seq_port_info_t *info;
-    snd_seq_port_subscribe_t *sub;
-    snd_seq_addr_t addr;
-    int err;
-
-    if (!desc) return pmInsufficientMemory;
-    
-    err = alsa_use_queue();
-    if (err < 0) goto free_desc;
-
-    snd_seq_port_info_alloca(&info);
-    snd_seq_port_info_set_port(info, midi->device_id);
-    snd_seq_port_info_set_capability(info, SND_SEQ_PORT_CAP_WRITE |
-                                     SND_SEQ_PORT_CAP_READ);
-    snd_seq_port_info_set_type(info, SND_SEQ_PORT_TYPE_MIDI_GENERIC | 
-                                     SND_SEQ_PORT_TYPE_APPLICATION);
-    snd_seq_port_info_set_port_specified(info, 1);
-    /* bugfix: without the following, it won't generate timestamp - PaulLiu */
-    snd_seq_port_info_set_timestamping(info, 1);
-    snd_seq_port_info_set_timestamp_real(info, 0);
-    snd_seq_port_info_set_timestamp_queue(info, queue);
-    err = snd_seq_create_port(seq, info);
-    if (err < 0) goto free_queue;
-
-    /* fill in fields of desc, which is passed to pm_write routines */
-    midi->descriptor = desc;
-    desc->client = GET_DESCRIPTOR_CLIENT(client_port);
-    desc->port = GET_DESCRIPTOR_PORT(client_port);
-    desc->this_port = midi->device_id;
-    desc->in_sysex = 0;
-
-    desc->error = 0;
-
-    VERBOSE printf("snd_seq_connect_from: %d %d %d\n", 
-                   desc->this_port, desc->client, desc->port);
-    snd_seq_port_subscribe_alloca(&sub);
-    addr.client = snd_seq_client_id(seq);
-    addr.port = desc->this_port;
-    snd_seq_port_subscribe_set_dest(sub, &addr);
-    addr.client = desc->client;
-    addr.port = desc->port;
-    snd_seq_port_subscribe_set_sender(sub, &addr);
-    snd_seq_port_subscribe_set_time_update(sub, 1);
-    /* this doesn't seem to work: messages come in with real timestamps */
-    snd_seq_port_subscribe_set_time_real(sub, 0);
-    err = snd_seq_subscribe_port(seq, sub);
-    /* err = 
-       snd_seq_connect_from(seq, desc->this_port, desc->client, desc->port); */
-    if (err < 0) goto free_this_port;  /* clean up and return on error */
-    return pmNoError;
-
- free_this_port:
-    snd_seq_delete_port(seq, desc->this_port);
- free_queue:
-    alsa_unuse_queue();
- free_desc:
-    pm_free(desc);
-    pm_hosterror = err;
-    if (err < 0) {
-        get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err);
-    }
-    return pmHostError;
-}
-
-static PmError alsa_in_close(PmInternal *midi)
-{
-    alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
-    if (!desc) return pmBadPtr;
-    if (pm_hosterror = snd_seq_disconnect_from(seq, desc->this_port, 
-                                               desc->client, desc->port)) {
-        snd_seq_delete_port(seq, desc->this_port); /* try to close port */
-    } else {
-        pm_hosterror = snd_seq_delete_port(seq, desc->this_port);
-    }
-    alsa_unuse_queue();
-    pm_free(desc);
-    if (pm_hosterror) {
-        get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, 
-                            pm_hosterror);
-        return pmHostError;
-    }
-    return pmNoError;
-}
-        
-
-static PmError alsa_abort(PmInternal *midi)
-{
-    /* NOTE: ALSA documentation is vague. This is supposed to 
-     * remove any pending output messages. If you can test and 
-     * confirm this code is correct, please update this comment. -RBD
-     */
-    /* Unfortunately, I can't even compile it -- my ALSA version 
-     * does not implement snd_seq_remove_events_t, so this does
-     * not compile. I'll try again, but it looks like I'll need to
-     * upgrade my entire Linux OS -RBD
-     */
-    /*
-    alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
-    snd_seq_remove_events_t info;
-    snd_seq_addr_t addr;
-    addr.client = desc->client;
-    addr.port = desc->port;
-    snd_seq_remove_events_set_dest(&info, &addr);
-    snd_seq_remove_events_set_condition(&info, SND_SEQ_REMOVE_DEST);
-    pm_hosterror = snd_seq_remove_events(seq, &info);
-    if (pm_hosterror) {
-        get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, 
-                            pm_hosterror);
-        return pmHostError;
-    }
-    */
-    printf("WARNING: alsa_abort not implemented\n");
-    return pmNoError;
-}
-
-
-#ifdef GARBAGE
-This is old code here temporarily for reference
-static PmError alsa_write(PmInternal *midi, PmEvent *buffer, long length)
-{
-    alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
-    int i, bytes;
-    unsigned char byte;
-    long msg;
-
-    desc->error = 0;
-    for (; length > 0; length--, buffer++) {
-        VERBOSE printf("message 0x%x\n", buffer->message);
-        if (Pm_MessageStatus(buffer->message) == MIDI_SYSEX)
-            desc->in_sysex = TRUE;
-        if (desc->in_sysex) {
-            msg = buffer->message;
-            for (i = 0; i < 4; i++) {
-                byte = msg;  /* extract next byte to send */
-                alsa_write_byte(midi, byte, buffer->timestamp);
-                if (byte == MIDI_EOX) {
-                    desc->in_sysex = FALSE;
-                    break;
-                }
-                if (desc->error < 0) break;
-                msg >>= 8; /* shift next byte into position */
-            }
-        } else {
-            bytes = midi_message_length(buffer->message);
-            msg = buffer->message;
-            for (i = 0; i < bytes; i++) {
-                byte = msg; /* extract next byte to send */
-                VERBOSE printf("sending 0x%x\n", byte);
-                alsa_write_byte(midi, byte, buffer->timestamp);
-                if (desc->error < 0) break;
-                msg >>= 8; /* shift next byte into position */
-            }
-        }
-    }
-    if (desc->error < 0) return pmHostError;
-
-    VERBOSE printf("snd_seq_drain_output: 0x%x\n", seq);
-    desc->error = snd_seq_drain_output(seq);
-    if (desc->error < 0) return pmHostError;
-
-    desc->error = pmNoError;
-    return pmNoError;
-}
-#endif
-
-
-static PmError alsa_write_flush(PmInternal *midi, PmTimestamp timestamp)
-{
-    alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
-    VERBOSE printf("snd_seq_drain_output: 0x%x\n", (unsigned int) seq);
-    desc->error = snd_seq_drain_output(seq);
-    if (desc->error < 0) return pmHostError;
-
-    desc->error = pmNoError;
-    return pmNoError;
-}
-
-
-static PmError alsa_write_short(PmInternal *midi, PmEvent *event)
-{
-    int bytes = midi_message_length(event->message);
-    long msg = event->message;
-    int i;
-    alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
-    for (i = 0; i < bytes; i++) {
-        unsigned char byte = msg;
-        VERBOSE printf("sending 0x%x\n", byte);
-        alsa_write_byte(midi, byte, event->timestamp);
-        if (desc->error < 0) break;
-        msg >>= 8; /* shift next byte into position */
-    }
-    if (desc->error < 0) return pmHostError;
-    desc->error = pmNoError;
-    return pmNoError;
-}
-
-
-/* alsa_sysex -- implements begin_sysex and end_sysex */
-PmError alsa_sysex(PmInternal *midi, PmTimestamp timestamp) {
-    return pmNoError;
-}
-
-
-static PmTimestamp alsa_synchronize(PmInternal *midi)
-{
-    return 0; /* linux implementation does not use this synchronize function */
-    /* Apparently, Alsa data is relative to the time you send it, and there
-       is no reference. If this is true, this is a serious shortcoming of
-       Alsa. If not true, then PortMidi has a serious shortcoming -- it 
-       should be scheduling relative to Alsa's time reference. */
-}
-
-
-static void handle_event(snd_seq_event_t *ev)
-{
-    int device_id = ev->dest.port;
-    PmInternal *midi = descriptors[device_id].internalDescriptor;
-    PmEvent pm_ev;
-    PmTimeProcPtr time_proc = midi->time_proc;
-    PmTimestamp timestamp;
-
-    /* time stamp should be in ticks, using our queue where 1 tick = 1ms */
-    assert((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_TICK);
-
-    /* if no time_proc, just return "native" ticks (ms) */
-    if (time_proc == NULL) {
-        timestamp = ev->time.tick;
-    } else { /* translate time to time_proc basis */
-        snd_seq_queue_status_t *queue_status;
-        snd_seq_queue_status_alloca(&queue_status);
-        snd_seq_get_queue_status(seq, queue, queue_status);
-        /* return (now - alsa_now) + alsa_timestamp */
-        timestamp = (*time_proc)(midi->time_info) + ev->time.tick -
-                    snd_seq_queue_status_get_tick_time(queue_status);
-    }
-    pm_ev.timestamp = timestamp;
-    switch (ev->type) {
-    case SND_SEQ_EVENT_NOTEON:
-        pm_ev.message = Pm_Message(0x90 | ev->data.note.channel,
-                                   ev->data.note.note & 0x7f,
-                                   ev->data.note.velocity & 0x7f);
-        pm_read_short(midi, &pm_ev);
-        break;
-    case SND_SEQ_EVENT_NOTEOFF:
-        pm_ev.message = Pm_Message(0x80 | ev->data.note.channel,
-                                   ev->data.note.note & 0x7f,
-                                   ev->data.note.velocity & 0x7f);
-        pm_read_short(midi, &pm_ev);
-        break;
-    case SND_SEQ_EVENT_KEYPRESS:
-        pm_ev.message = Pm_Message(0xa0 | ev->data.note.channel,
-                                   ev->data.note.note & 0x7f,
-                                   ev->data.note.velocity & 0x7f);
-        pm_read_short(midi, &pm_ev);
-        break;
-    case SND_SEQ_EVENT_CONTROLLER:
-        pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
-                                   ev->data.control.param & 0x7f,
-                                   ev->data.control.value & 0x7f);
-        pm_read_short(midi, &pm_ev);
-        break;
-    case SND_SEQ_EVENT_PGMCHANGE:
-        pm_ev.message = Pm_Message(0xc0 | ev->data.note.channel,
-                                   ev->data.control.value & 0x7f, 0);
-        pm_read_short(midi, &pm_ev);
-        break;
-    case SND_SEQ_EVENT_CHANPRESS:
-        pm_ev.message = Pm_Message(0xd0 | ev->data.note.channel,
-                                   ev->data.control.value & 0x7f, 0);
-        pm_read_short(midi, &pm_ev);
-        break;
-    case SND_SEQ_EVENT_PITCHBEND:
-        pm_ev.message = Pm_Message(0xe0 | ev->data.note.channel,
-                            (ev->data.control.value + 0x2000) & 0x7f,
-                            ((ev->data.control.value + 0x2000) >> 7) & 0x7f);
-        pm_read_short(midi, &pm_ev);
-        break;
-    case SND_SEQ_EVENT_CONTROL14:
-        if (ev->data.control.param < 0x20) {
-            pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
-                                       ev->data.control.param,
-                                       (ev->data.control.value >> 7) & 0x7f);
-            pm_read_short(midi, &pm_ev);
-            pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
-                                       ev->data.control.param + 0x20,
-                                       ev->data.control.value & 0x7f);
-            pm_read_short(midi, &pm_ev);
-        } else {
-            pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
-                                       ev->data.control.param & 0x7f,
-                                       ev->data.control.value & 0x7f);
-
-            pm_read_short(midi, &pm_ev);
-        }
-        break;
-    case SND_SEQ_EVENT_SONGPOS:
-        pm_ev.message = Pm_Message(0xf2,
-                                   ev->data.control.value & 0x7f,
-                                   (ev->data.control.value >> 7) & 0x7f);
-        pm_read_short(midi, &pm_ev);
-        break;
-    case SND_SEQ_EVENT_SONGSEL:
-        pm_ev.message = Pm_Message(0xf3,
-                                   ev->data.control.value & 0x7f, 0);
-        pm_read_short(midi, &pm_ev);
-        break;
-    case SND_SEQ_EVENT_QFRAME:
-        pm_ev.message = Pm_Message(0xf1,
-                                   ev->data.control.value & 0x7f, 0);
-        pm_read_short(midi, &pm_ev);
-        break;
-    case SND_SEQ_EVENT_START:
-        pm_ev.message = Pm_Message(0xfa, 0, 0);
-        pm_read_short(midi, &pm_ev);
-        break;
-    case SND_SEQ_EVENT_CONTINUE:
-        pm_ev.message = Pm_Message(0xfb, 0, 0);
-        pm_read_short(midi, &pm_ev);
-        break;
-    case SND_SEQ_EVENT_STOP:
-        pm_ev.message = Pm_Message(0xfc, 0, 0);
-        pm_read_short(midi, &pm_ev);
-        break;
-    case SND_SEQ_EVENT_CLOCK:
-        pm_ev.message = Pm_Message(0xf8, 0, 0);
-        pm_read_short(midi, &pm_ev);
-        break;
-    case SND_SEQ_EVENT_TUNE_REQUEST:
-        pm_ev.message = Pm_Message(0xf6, 0, 0);
-        pm_read_short(midi, &pm_ev);
-        break;
-    case SND_SEQ_EVENT_RESET:
-        pm_ev.message = Pm_Message(0xff, 0, 0);
-        pm_read_short(midi, &pm_ev);
-        break;
-    case SND_SEQ_EVENT_SENSING:
-        pm_ev.message = Pm_Message(0xfe, 0, 0);
-        pm_read_short(midi, &pm_ev);
-        break;
-    case SND_SEQ_EVENT_SYSEX: {
-        const BYTE *ptr = (const BYTE *) ev->data.ext.ptr;
-        /* assume there is one sysex byte to process */
-        pm_read_bytes(midi, ptr, ev->data.ext.len, timestamp);
-        break;
-    }
-    /* TODO: must handle port close messsage - PaulLiu */
-    //case SND_SEQ_EVENT_UNSUBSCRIBE: {
-    //}
-    default:
-        printf("not handled type %x\n", ev->type);
-    }
-}
-
-
-static PmError alsa_poll(PmInternal *midi)
-{
-    snd_seq_event_t *ev;
-    /* expensive check for input data, gets data from device: */
-    while (snd_seq_event_input_pending(seq, TRUE) > 0) {
-        /* cheap check on local input buffer */
-        while (snd_seq_event_input_pending(seq, FALSE) > 0) {
-            /* check for and ignore errors, e.g. input overflow */
-            /* note: if there's overflow, this should be reported
-             * all the way through to client. Since input from all
-             * devices is merged, we need to find all input devices
-             * and set all to the overflow state.
-             * NOTE: this assumes every input is ALSA based.
-             */
-            int rslt = snd_seq_event_input(seq, &ev);
-            if (rslt >= 0) {
-                handle_event(ev);
-            } else if (rslt == -ENOSPC) {
-                int i;
-                for (i = 0; i < pm_descriptor_index; i++) {
-                    if (descriptors[i].pub.input) {
-                        PmInternal *midi = (PmInternal *) 
-                                descriptors[i].internalDescriptor;
-                        /* careful, device may not be open! */
-                        if (midi) Pm_SetOverflow(midi->queue);
-                    }
-                }
-            }
-        }
-    }
-    return pmNoError;
-}
-
-
-static unsigned int alsa_has_host_error(PmInternal *midi)
-{
-    alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
-    return desc->error;
-}
-
-
-static void alsa_get_host_error(PmInternal *midi, char *msg, unsigned int len)
-{
-    alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
-    int err = (pm_hosterror || desc->error);
-    get_alsa_error_text(msg, len, err);
-}
-
-
-pm_fns_node pm_linuxalsa_in_dictionary = {
-    none_write_short,
-    none_sysex,
-    none_sysex,
-    none_write_byte,
-    none_write_short,
-    none_write_flush,
-    alsa_synchronize,
-    alsa_in_open,
-    alsa_abort,
-    alsa_in_close,
-    alsa_poll,
-    alsa_has_host_error,
-    alsa_get_host_error
-};
-
-pm_fns_node pm_linuxalsa_out_dictionary = {
-    alsa_write_short,
-    alsa_sysex,
-    alsa_sysex,
-    alsa_write_byte,
-    alsa_write_short, /* short realtime message */
-    alsa_write_flush,
-    alsa_synchronize,
-    alsa_out_open, 
-    alsa_abort, 
-    alsa_out_close,
-    none_poll,
-    alsa_has_host_error,
-    alsa_get_host_error
-};
-
-
-/* pm_strdup -- copy a string to the heap. Use this rather than strdup so 
- *    that we call pm_alloc, not malloc. This allows portmidi to avoid 
- *    malloc which might cause priority inversion. Probably ALSA is going
- *    to call malloc anyway, so this extra work here may be pointless.
- */
-char *pm_strdup(const char *s)
-{
-    int len = strlen(s);
-    char *dup = (char *) pm_alloc(len + 1);
-    strcpy(dup, s);
-    return dup;
-}
-
-
-PmError pm_linuxalsa_init( void )
-{
-    int  err;
-    snd_seq_client_info_t *cinfo;
-    snd_seq_port_info_t *pinfo;
-    unsigned int caps;
-
-    /* Previously, the last parameter was SND_SEQ_NONBLOCK, but this 
-     * would cause messages to be dropped if the ALSA buffer fills up.
-     * The correct behavior is for writes to block until there is 
-     * room to send all the data. The client should normally allocate
-     * a large enough buffer to avoid blocking on output. 
-     * Now that blocking is enabled, the seq_event_input() will block
-     * if there is no input data. This is not what we want, so must
-     * call seq_event_input_pending() to avoid blocking.
-     */
-    err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
-    if (err < 0) return err;
-    
-    snd_seq_client_info_alloca(&cinfo);
-    snd_seq_port_info_alloca(&pinfo);
-
-    snd_seq_client_info_set_client(cinfo, -1);
-    while (snd_seq_query_next_client(seq, cinfo) == 0) {
-        snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
-        snd_seq_port_info_set_port(pinfo, -1);
-        while (snd_seq_query_next_port(seq, pinfo) == 0) {
-            if (snd_seq_port_info_get_client(pinfo) == SND_SEQ_CLIENT_SYSTEM)
-                continue; /* ignore Timer and Announce ports on client 0 */
-            caps = snd_seq_port_info_get_capability(pinfo);
-            if (!(caps & (SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE)))
-                continue; /* ignore if you cannot read or write port */
-            if (caps & SND_SEQ_PORT_CAP_SUBS_WRITE) {
-                if (pm_default_output_device_id == -1) 
-                    pm_default_output_device_id = pm_descriptor_index;
-                pm_add_device("ALSA",
-                              pm_strdup(snd_seq_port_info_get_name(pinfo)),
-                              FALSE,
-                              MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),
-                                              snd_seq_port_info_get_port(pinfo)),
-                              &pm_linuxalsa_out_dictionary);
-            }
-            if (caps & SND_SEQ_PORT_CAP_SUBS_READ) {
-                if (pm_default_input_device_id == -1) 
-                    pm_default_input_device_id = pm_descriptor_index;
-                pm_add_device("ALSA",
-                              pm_strdup(snd_seq_port_info_get_name(pinfo)),
-                              TRUE,
-                              MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),
-                                              snd_seq_port_info_get_port(pinfo)),
-                              &pm_linuxalsa_in_dictionary);
-            }
-        }
-    }
-    return pmNoError;
-}
-    
-
-void pm_linuxalsa_term(void)
-{
-    if (seq) {
-        snd_seq_close(seq);
-        pm_free(descriptors);
-        descriptors = NULL;
-        pm_descriptor_index = 0;
-        pm_descriptor_max = 0;
-    }
-}
+/*+ * pmlinuxalsa.c -- system specific definitions+ * + * written by:+ *  Roger Dannenberg (port to Alsa 0.9.x)+ *  Clemens Ladisch (provided code examples and invaluable consulting)+ *  Jason Cohen, Rico Colon, Matt Filippone (Alsa 0.5.x implementation)+ */ ++/* omit this code if PMALSA is not defined -- this mechanism allows+ * selection of different MIDI interfaces (at compile time).+ */+#ifdef PMALSA++#include "stdlib.h"+#include "portmidi.h"+#include "pmutil.h"+#include "pminternal.h"+#include "pmlinuxalsa.h"+#include "string.h"+#include "porttime.h"++#include <alsa/asoundlib.h>++/* I used many print statements to debug this code. I left them in the+ * source, and you can turn them on by changing false to true below:+ */+#define VERBOSE_ON 0+#define VERBOSE if (VERBOSE_ON)++#define MIDI_SYSEX      0xf0+#define MIDI_EOX        0xf7++#if SND_LIB_MAJOR == 0 && SND_LIB_MINOR < 9+#error needs ALSA 0.9.0 or later+#endif++/* to store client/port in the device descriptor */+#define MAKE_DESCRIPTOR(client, port) ((void*)(long)(((client) << 8) | (port)))+#define GET_DESCRIPTOR_CLIENT(info) ((((long)(info)) >> 8) & 0xff)+#define GET_DESCRIPTOR_PORT(info) (((long)(info)) & 0xff)++#define BYTE unsigned char++extern pm_fns_node pm_linuxalsa_in_dictionary;+extern pm_fns_node pm_linuxalsa_out_dictionary;++static snd_seq_t *seq = NULL; /* all input comes here, +                                 output queue allocated on seq */+static int queue, queue_used; /* one for all ports, reference counted */++#define PORT_IS_CLOSED -999999++typedef struct alsa_info_struct {+    int is_virtual;+    int client;+    int port;+    int this_port;+    int in_sysex;+    snd_midi_event_t *parser;+} alsa_info_node, *alsa_info_type;+++/* get_alsa_error_text -- copy error text to potentially short string */+/**/+static void get_alsa_error_text(char *msg, int len, int err)+{+    int errlen = strlen(snd_strerror(err));+    if (errlen > 0 && errlen < len) {+        strcpy(msg, snd_strerror(err));+    } else if (len > 20) {+        sprintf(msg, "Alsa error %d", err);+    } else {+        msg[0] = 0;+    }+}+++static PmError check_hosterror(int err)+{+    if (err < 0) {+        get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err);+        pm_hosterror = TRUE;+        return pmHostError;+    }+    return pmNoError;+}+++/* queue is shared by both input and output, reference counted */+static PmError alsa_use_queue(void)+{+    int err = 0;+    if (queue_used == 0) {+        snd_seq_queue_tempo_t *tempo;++        queue = snd_seq_alloc_queue(seq);+        if (queue < 0) {+            return check_hosterror(queue);+        }+        snd_seq_queue_tempo_alloca(&tempo);+        snd_seq_queue_tempo_set_tempo(tempo, 480000);+        snd_seq_queue_tempo_set_ppq(tempo, 480);+        err = snd_seq_set_queue_tempo(seq, queue, tempo);+        if (err < 0) {+            return check_hosterror(err);+        }+        snd_seq_start_queue(seq, queue, NULL);+        snd_seq_drain_output(seq);+    }+    ++queue_used;+    return pmNoError;+}+++static void alsa_unuse_queue(void)+{+    if (--queue_used == 0) {+        snd_seq_stop_queue(seq, queue, NULL);+        snd_seq_drain_output(seq);+        snd_seq_free_queue(seq, queue);+        VERBOSE printf("queue freed\n");+    }+}+++/* midi_message_length -- how many bytes in a message? */+static int midi_message_length(PmMessage message)+{+    message &= 0xff;+    if (message < 0x80) {+        return 0;+    } else if (message < 0xf0) {+        static const int length[] = {3, 3, 3, 3, 2, 2, 3};+        return length[(message - 0x80) >> 4];+    } else {+        static const int length[] = {+            -1, 2, 3, 2, 0, 0, 1, -1, 1, 0, 1, 1, 1, 0, 1, 1};+        return length[message - 0xf0];+    }+}+++static alsa_info_type alsa_info_create(int client_port, long id, int is_virtual)+{+    alsa_info_type info = (alsa_info_type) pm_alloc(sizeof(alsa_info_node));+    info->is_virtual = is_virtual;+    info->this_port = id;+    info->client = GET_DESCRIPTOR_CLIENT(client_port);+    info->port = GET_DESCRIPTOR_PORT(client_port);+    info->in_sysex = 0;+    return info;+}    +++/* search system dependent extra parameters for string */+static const char *get_sysdep_name(enum PmSysDepPropertyKey key,+                                   PmSysDepInfo *info)+{+    /* the version where all current properties were introduced is 210 */+    if (info && info->structVersion >= 210) {+        int i;+        for (i = 0; i < info->length; i++) {  /* search for key */+            if (info->properties[i].key == key) {+                return info->properties[i].value;+            }+        }+    }+    return NULL;+}+++static void maybe_set_client_name(PmSysDepInfo *driverInfo)+{+    /* make sure seq is created and we have info */+    if (!seq || !driverInfo) {+        return;+    }+    +    const char *client_name = get_sysdep_name(pmKeyAlsaClientName,+                                              (PmSysDepInfo *) driverInfo);+    if (client_name) {+        snd_seq_set_client_name(seq, client_name);+        VERBOSE printf("maybe_set_client_name set client to %s\n", client_name);+    }+}    +++static PmError alsa_out_open(PmInternal *midi, void *driverInfo) +{+    int id = midi->device_id;+    void *client_port = pm_descriptors[id].descriptor;+    alsa_info_type ainfo = alsa_info_create((long) client_port, id,+                                            pm_descriptors[id].pub.is_virtual);+    snd_seq_port_info_t *pinfo;+    int err = 0;+    int using_the_queue = 0;++    if (!ainfo) return pmInsufficientMemory;+    midi->api_info = ainfo;++    snd_seq_port_info_alloca(&pinfo);+    if (!ainfo->is_virtual) {+        snd_seq_port_info_set_port(pinfo, id);+        snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE |+                SND_SEQ_PORT_CAP_READ  | SND_SEQ_PORT_CAP_SUBS_READ);+        snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | +                                          SND_SEQ_PORT_TYPE_APPLICATION);+        const char *port_name = get_sysdep_name(pmKeyAlsaPortName,+                                                (PmSysDepInfo *) driverInfo);+        if (port_name) {+            snd_seq_port_info_set_name(pinfo, port_name);+        }+        snd_seq_port_info_set_port_specified(pinfo, 1);++        err = snd_seq_create_port(seq, pinfo);+        if (err < 0) goto free_ainfo;++    }+    +    err = snd_midi_event_new(PM_DEFAULT_SYSEX_BUFFER_SIZE, &ainfo->parser);+    if (err < 0) goto free_this_port;++    if (midi->latency > 0) { /* must delay output using a queue */+        err = alsa_use_queue();+        if (err < 0) goto free_parser;+        using_the_queue++;+    }++    if (!ainfo->is_virtual) {+        err = snd_seq_connect_to(seq, ainfo->this_port, ainfo->client,+                                 ainfo->port);+        if (err < 0) goto unuse_queue;  /* clean up and return on error */+    }++    maybe_set_client_name(driverInfo);++    return pmNoError;++ unuse_queue:+    if (using_the_queue > 0)  /* only for latency>0 case */+        alsa_unuse_queue();+ free_parser:+    snd_midi_event_free(ainfo->parser);+ free_this_port:+    snd_seq_delete_port(seq, ainfo->this_port);+ free_ainfo:+    pm_free(ainfo);+    return check_hosterror(err);+}+    ++static PmError alsa_write_byte(PmInternal *midi, unsigned char byte, +                               PmTimestamp timestamp)+{+    alsa_info_type info = (alsa_info_type) midi->api_info;+    snd_seq_event_t ev;+    int err = 0;++    snd_seq_ev_clear(&ev);+    if (snd_midi_event_encode_byte(info->parser, byte, &ev) == 1) {+        if (info->is_virtual) {+            snd_seq_ev_set_subs(&ev);+        } else {+            snd_seq_ev_set_dest(&ev, info->client, info->port);+        }+        snd_seq_ev_set_source(&ev, info->this_port);+        if (midi->latency > 0) {+            /* compute relative time of event = timestamp - now + latency */+            PmTimestamp now = (midi->time_proc ? +                               midi->time_proc(midi->time_info) : +                               Pt_Time());+            int when = timestamp;+            /* if timestamp is zero, send immediately */+            /* otherwise compute time delay and use delay if positive */+            if (when == 0) when = now;+            when = (when - now) + midi->latency;+            if (when < 0) when = 0;+            VERBOSE printf("timestamp %d now %d latency %d, ", +                           (int) timestamp, (int) now, midi->latency);+            VERBOSE printf("scheduling event after %d\n", when);+            /* message is sent in relative ticks, where 1 tick = 1 ms */+            snd_seq_ev_schedule_tick(&ev, queue, 1, when);+            /* NOTE: for cases where the user does not supply a time function,+               we could optimize the code by not starting Pt_Time and using+               the alsa tick time instead. I didn't do this because it would+               entail changing the queue management to start the queue tick+               count when PortMidi is initialized and keep it running until+               PortMidi is terminated. (This should be simple, but it's not+               how the code works now.) -RBD */+        } else { /* send event out without queueing */+            VERBOSE printf("direct\n");+            /* ev.queue = SND_SEQ_QUEUE_DIRECT;+               ev.dest.client = SND_SEQ_ADDRESS_SUBSCRIBERS; */+            snd_seq_ev_set_direct(&ev);+        }+        VERBOSE printf("sending event, timestamp %d (%d+%dns) (%s, %s)\n",+                       ev.time.tick, ev.time.time.tv_sec, ev.time.time.tv_nsec,+                       (ev.flags & SND_SEQ_TIME_STAMP_MASK ? "real" : "tick"),+                       (ev.flags & SND_SEQ_TIME_MODE_MASK ? "rel" : "abs"));+        err = snd_seq_event_output(seq, &ev);+    }+    return check_hosterror(err);+}+++static PmError alsa_out_close(PmInternal *midi)+{+    alsa_info_type info = (alsa_info_type) midi->api_info;+    int err = 0;+    int error2 = 0;+    if (!info) return pmBadPtr;++    if (info->this_port != PORT_IS_CLOSED) {+        if (!info->is_virtual) {+            err = snd_seq_disconnect_to(seq, info->this_port,+                                        info->client, info->port);+            /* even if there was an error, we still try to delete the port */+            error2 = snd_seq_delete_port(seq, info->this_port);++            if (!err) {  /* retain original error if there was one */+                err = error2; /* otherwise, use port delete status */+            }+        }+    }+    if (midi->latency > 0) alsa_unuse_queue();+    snd_midi_event_free(info->parser);+    midi->api_info = NULL; /* destroy the pointer to signify "closed" */+    pm_free(info);+    return check_hosterror(err);+}+++static PmError alsa_create_virtual(int is_input, const char *name,+                                   void *device_info)+{+    snd_seq_port_info_t *pinfo;+    int err;+    int client, port;+    +    /* we need the id to set the port. */+    PmDeviceID id = pm_add_device("ALSA", name, is_input, TRUE, NULL,+                                  (is_input ? &pm_linuxalsa_in_dictionary :+                                              &pm_linuxalsa_out_dictionary));+    if (id < 0) { /* error -- out of memory? */+        return id;+    }+    snd_seq_port_info_alloca(&pinfo);+    snd_seq_port_info_set_capability(pinfo,+            (is_input ? SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE :+                        SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ));+    snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC |+                                     SND_SEQ_PORT_TYPE_APPLICATION);+    snd_seq_port_info_set_name(pinfo, name);+    snd_seq_port_info_set_port(pinfo, id);+    snd_seq_port_info_set_port_specified(pinfo, 1);+    /* next 3 lines needed to generate timestamp - PaulLiu */+    snd_seq_port_info_set_timestamping(pinfo, 1);+    snd_seq_port_info_set_timestamp_real(pinfo, 0);+    snd_seq_port_info_set_timestamp_queue(pinfo, queue);++    err = snd_seq_create_port(seq, pinfo);+    if (err < 0) {+        pm_undo_add_device(id);+        return check_hosterror(err);+    }++    client = snd_seq_port_info_get_client(pinfo);+    port = snd_seq_port_info_get_port(pinfo);+    pm_descriptors[id].descriptor = MAKE_DESCRIPTOR(client, port);+    return id;+}+++ static PmError alsa_delete_virtual(PmDeviceID id)+ {+     int err = snd_seq_delete_port(seq, id);+     return check_hosterror(err);+ }+ ++static PmError alsa_in_open(PmInternal *midi, void *driverInfo)+{+    int id = midi->device_id;+    void *client_port = pm_descriptors[id].descriptor;+    alsa_info_type ainfo = alsa_info_create((long) client_port, id,+                                            pm_descriptors[id].pub.is_virtual);+    snd_seq_port_info_t *pinfo;+    snd_seq_port_subscribe_t *sub;+    snd_seq_addr_t addr;+    int err = 0;+    int is_virtual = pm_descriptors[id].pub.is_virtual;+    +    if (!ainfo) return pmInsufficientMemory;+    midi->api_info = ainfo;++    err = alsa_use_queue();+    if (err < 0) goto free_ainfo;++    snd_seq_port_info_alloca(&pinfo);+    if (is_virtual) {+        ainfo->is_virtual = TRUE;+        if (snd_seq_get_port_info(seq, ainfo->port, pinfo)) {+            pinfo = NULL;+            goto free_ainfo;+        }+    } else {+        /* create a port for this alsa client (seq) where the port+           number matches the portmidi device ID of the input device */+        snd_seq_port_info_set_port(pinfo, id);+        snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE |+                SND_SEQ_PORT_CAP_READ  | SND_SEQ_PORT_CAP_SUBS_WRITE);++        snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | +                                          SND_SEQ_PORT_TYPE_APPLICATION);+        snd_seq_port_info_set_port_specified(pinfo, 1);+        +        const char *port_name = get_sysdep_name(pmKeyAlsaPortName,+                                                (PmSysDepInfo *) driverInfo);+        if (port_name) {+            snd_seq_port_info_set_name(pinfo, port_name);+        }+        +        err = snd_seq_create_port(seq, pinfo);+        if (err < 0) goto free_queue;++        /* forward messages from input to this alsa client, so this+         * alsa client is the destination, and the destination port is the+         * port we just created using the device ID as port number+         */+        snd_seq_port_subscribe_alloca(&sub);+        addr.client = snd_seq_client_id(seq);+        addr.port = ainfo->this_port;+        snd_seq_port_subscribe_set_dest(sub, &addr);++        /* forward from the sender which is the device named by +           client and port */+        addr.client = ainfo->client;+        addr.port = ainfo->port;+        snd_seq_port_subscribe_set_sender(sub, &addr);+        snd_seq_port_subscribe_set_time_update(sub, 1);+        /* this doesn't seem to work: messages come in with real timestamps */+        snd_seq_port_subscribe_set_time_real(sub, 0);+        err = snd_seq_subscribe_port(seq, sub);+        if (err < 0) goto free_this_port;  /* clean up and return on error */+    }++    maybe_set_client_name(driverInfo);++    return pmNoError;+ free_this_port:+    snd_seq_delete_port(seq, ainfo->this_port);+ free_queue:+    alsa_unuse_queue();+ free_ainfo:+    pm_free(ainfo);+    return check_hosterror(err);+}++static PmError alsa_in_close(PmInternal *midi)+{+    int err = 0;+    alsa_info_type info = (alsa_info_type) midi->api_info;+    if (!info) return pmBadPtr;+    /* virtual ports stay open because the represent devices */+    if (!info->is_virtual && info->this_port != PORT_IS_CLOSED) {+        err = snd_seq_delete_port(seq, info->this_port);+    }+    alsa_unuse_queue();+    midi->api_info = NULL;+    pm_free(info);+    return check_hosterror(err);+}+        ++static PmError alsa_abort(PmInternal *midi)+{+    /* NOTE: ALSA documentation is vague. This is supposed to +     * remove any pending output messages. If you can test and +     * confirm this code is correct, please update this comment. -RBD+     */+    /* Unfortunately, I can't even compile it -- my ALSA version +     * does not implement snd_seq_remove_events_t, so this does+     * not compile. I'll try again, but it looks like I'll need to+     * upgrade my entire Linux OS -RBD+     */+    /*+    info_type info = (info_type) midi->api_info;+    snd_seq_remove_events_t info;+    snd_seq_addr_t addr;+    addr.client = info->client;+    addr.port = info->port;+    snd_seq_remove_events_set_dest(&info, &addr);+    snd_seq_remove_events_set_condition(&info, SND_SEQ_REMOVE_DEST);+    pm_hosterror = snd_seq_remove_events(seq, &info);+    if (pm_hosterror) {+        get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, +                            pm_hosterror);+        return pmHostError;+    }+    */+    printf("WARNING: alsa_abort not implemented\n");+    return pmNoError;+}+++static PmError alsa_write_flush(PmInternal *midi, PmTimestamp timestamp)+{+    int err;+    alsa_info_type info = (alsa_info_type) midi->api_info;+    if (!info) return pmBadPtr;+    VERBOSE printf("snd_seq_drain_output: %p\n", seq);+    err = snd_seq_drain_output(seq);+    return check_hosterror(err);+}+++static PmError alsa_write_short(PmInternal *midi, PmEvent *event)+{+    int bytes = midi_message_length(event->message);+    PmMessage msg = event->message;+    int i;+    alsa_info_type info = (alsa_info_type) midi->api_info;+    if (!info) return pmBadPtr;+    for (i = 0; i < bytes; i++) {+        unsigned char byte = msg;+        VERBOSE printf("sending 0x%x\n", byte);+        alsa_write_byte(midi, byte, event->timestamp);+        if (pm_hosterror) break;+        msg >>= 8; /* shift next byte into position */+    }+    if (pm_hosterror) return pmHostError;+    return pmNoError;+}+++/* alsa_sysex -- implements begin_sysex and end_sysex */+PmError alsa_sysex(PmInternal *midi, PmTimestamp timestamp) {+    return pmNoError;+}+++static PmTimestamp alsa_synchronize(PmInternal *midi)+{+    return 0; /* linux implementation does not use this synchronize function */+    /* Apparently, Alsa data is relative to the time you send it, and there+       is no reference. If this is true, this is a serious shortcoming of+       Alsa. If not true, then PortMidi has a serious shortcoming -- it +       should be scheduling relative to Alsa's time reference. */+}+++static void handle_event(snd_seq_event_t *ev)+{+    int device_id = ev->dest.port;++    assert(device_id >= 0 && device_id < pm_descriptor_len);+    assert(pm_descriptors[device_id].pub.name);++    PmInternal *midi = pm_descriptors[device_id].pm_internal;+    /* There is a race condition when closing a device and+       continuing to poll other open devices. The closed device may+       have outstanding events from before the close operation.+    */+    if (!midi) {+        return;+    }+    PmEvent pm_ev;++    /* Copilot says: "snd_seq_event_input() will not return events for a+     * MIDI output device." However, this does not seem to be true, e.g.,+     * for MIDIFLEX 4 on Arch Linux, so we want to check and ignore the+     * call if this is an output device.+     */+    if (!midi->is_input) {+        return;+    }++    if (!midi->time_proc) {  /* extra sanity check */+        return;+    }++    PmTimestamp timestamp = midi->time_proc(midi->time_info);++    /* time stamp should be in ticks, using our queue where 1 tick = 1ms */+    /* assert((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_TICK);+       Currently, event timestamp is ignored. See long note below. +    */+    VERBOSE {+        /* translate time to time_proc basis */+        snd_seq_queue_status_t *queue_status;+        snd_seq_queue_status_alloca(&queue_status);+        snd_seq_get_queue_status(seq, queue, queue_status);+        printf("handle_event: alsa_now %d, "+               "event timestamp %d (%d+%dns) (%s, %s)\n",+               snd_seq_queue_status_get_tick_time(queue_status),+               ev->time.tick, ev->time.time.tv_sec, ev->time.time.tv_nsec,+               (ev->flags & SND_SEQ_TIME_STAMP_MASK ? "real" : "tick"),+               (ev->flags & SND_SEQ_TIME_MODE_MASK ? "rel" : "abs"));+        /* OLD: portmidi timestamp is (now - alsa_now) + alsa_timestamp */+        /* timestamp = (*time_proc)(midi->time_info) + ev->time.tick -+                       snd_seq_queue_status_get_tick_time(queue_status); */+    }+    /* CURRENT: portmidi timestamp is "now". In a test, timestamps from+     * hardware (MIDI over USB) were timestamped with the current ALSA+     * time (snd_seq_queue_status_get_tick_time) and flags indicating +     * absolute ticks, but timestamps from another application's virtual+     * port, sent direct with 0 absolute ticks, were received with a +     * large value that is apparently the time since the start time of+     * the other application. Without any reference to our local time,+     * this seems useless. PortMidi is supposed to return the local+     * PortMidi time of the arrival of the message, so the best we can+     * do is set the timestamp to our local clock.  This seems to be a+     * design flaw in ALSA -- I pointed this out a decade ago, but if+     * there is a workaround, I'd still like to know. Maybe there is a+     * way to use absolute real time and maybe that's sharable across+     * applications by referencing the system time?+     */+    pm_ev.timestamp = timestamp;+    switch (ev->type) {+    case SND_SEQ_EVENT_NOTEON:+        pm_ev.message = Pm_Message(0x90 | ev->data.note.channel,+                                   ev->data.note.note & 0x7f,+                                   ev->data.note.velocity & 0x7f);+        pm_read_short(midi, &pm_ev);+        break;+    case SND_SEQ_EVENT_NOTEOFF:+        pm_ev.message = Pm_Message(0x80 | ev->data.note.channel,+                                   ev->data.note.note & 0x7f,+                                   ev->data.note.velocity & 0x7f);+        pm_read_short(midi, &pm_ev);+        break;+    case SND_SEQ_EVENT_KEYPRESS:+        pm_ev.message = Pm_Message(0xa0 | ev->data.note.channel,+                                   ev->data.note.note & 0x7f,+                                   ev->data.note.velocity & 0x7f);+        pm_read_short(midi, &pm_ev);+        break;+    case SND_SEQ_EVENT_CONTROLLER:+        pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,+                                   ev->data.control.param & 0x7f,+                                   ev->data.control.value & 0x7f);+        pm_read_short(midi, &pm_ev);+        break;+    case SND_SEQ_EVENT_PGMCHANGE:+        pm_ev.message = Pm_Message(0xc0 | ev->data.note.channel,+                                   ev->data.control.value & 0x7f, 0);+        pm_read_short(midi, &pm_ev);+        break;+    case SND_SEQ_EVENT_CHANPRESS:+        pm_ev.message = Pm_Message(0xd0 | ev->data.note.channel,+                                   ev->data.control.value & 0x7f, 0);+        pm_read_short(midi, &pm_ev);+        break;+    case SND_SEQ_EVENT_PITCHBEND:+        pm_ev.message = Pm_Message(0xe0 | ev->data.note.channel,+                            (ev->data.control.value + 0x2000) & 0x7f,+                            ((ev->data.control.value + 0x2000) >> 7) & 0x7f);+        pm_read_short(midi, &pm_ev);+        break;+    case SND_SEQ_EVENT_CONTROL14:+        if (ev->data.control.param < 0x20) {+            pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,+                                       ev->data.control.param,+                                       (ev->data.control.value >> 7) & 0x7f);+            pm_read_short(midi, &pm_ev);+            pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,+                                       ev->data.control.param + 0x20,+                                       ev->data.control.value & 0x7f);+            pm_read_short(midi, &pm_ev);+        } else {+            pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,+                                       ev->data.control.param & 0x7f,+                                       ev->data.control.value & 0x7f);++            pm_read_short(midi, &pm_ev);+        }+        break;+    case SND_SEQ_EVENT_SONGPOS:+        pm_ev.message = Pm_Message(0xf2,+                                   ev->data.control.value & 0x7f,+                                   (ev->data.control.value >> 7) & 0x7f);+        pm_read_short(midi, &pm_ev);+        break;+    case SND_SEQ_EVENT_SONGSEL:+        pm_ev.message = Pm_Message(0xf3,+                                   ev->data.control.value & 0x7f, 0);+        pm_read_short(midi, &pm_ev);+        break;+    case SND_SEQ_EVENT_QFRAME:+        pm_ev.message = Pm_Message(0xf1,+                                   ev->data.control.value & 0x7f, 0);+        pm_read_short(midi, &pm_ev);+        break;+    case SND_SEQ_EVENT_START:+        pm_ev.message = Pm_Message(0xfa, 0, 0);+        pm_read_short(midi, &pm_ev);+        break;+    case SND_SEQ_EVENT_CONTINUE:+        pm_ev.message = Pm_Message(0xfb, 0, 0);+        pm_read_short(midi, &pm_ev);+        break;+    case SND_SEQ_EVENT_STOP:+        pm_ev.message = Pm_Message(0xfc, 0, 0);+        pm_read_short(midi, &pm_ev);+        break;+    case SND_SEQ_EVENT_CLOCK:+        pm_ev.message = Pm_Message(0xf8, 0, 0);+        pm_read_short(midi, &pm_ev);+        break;+    case SND_SEQ_EVENT_TUNE_REQUEST:+        pm_ev.message = Pm_Message(0xf6, 0, 0);+        pm_read_short(midi, &pm_ev);+        break;+    case SND_SEQ_EVENT_RESET:+        pm_ev.message = Pm_Message(0xff, 0, 0);+        pm_read_short(midi, &pm_ev);+        break;+    case SND_SEQ_EVENT_SENSING:+        pm_ev.message = Pm_Message(0xfe, 0, 0);+        pm_read_short(midi, &pm_ev);+        break;+    case SND_SEQ_EVENT_SYSEX: {+        const BYTE *ptr = (const BYTE *) ev->data.ext.ptr;+        /* assume there is one sysex byte to process */+        pm_read_bytes(midi, ptr, ev->data.ext.len, timestamp);+        break;+    }+    case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: {+        /* this happens if you have an input port open and the +         * device or application with virtual ports closes. We+         * mark the port as closed to avoid closing a 2nd time+         * when Pm_Close() is called.+         */+        alsa_info_type info = (alsa_info_type) midi->api_info;+        /* printf("SND_SEQ_EVENT_UNSUBSCRIBE message\n"); */+        info->this_port = PORT_IS_CLOSED;+        break;+    }+    case SND_SEQ_EVENT_PORT_SUBSCRIBED:+        break;  /* someone connected to a virtual output port, not reported */+    default:+        printf("portmidi handle_event: not handled type %x\n", ev->type);+        break;+    }+}+++static PmError alsa_poll(PmInternal *midi)+{+    if (!midi) {+        return pmBadPtr;+    }+    snd_seq_event_t *ev;+    /* expensive check for input data, gets data from device: */+    while (snd_seq_event_input_pending(seq, TRUE) > 0) {+        /* cheap check on local input buffer */+        while (snd_seq_event_input_pending(seq, FALSE) > 0) {+            /* check for and ignore errors, e.g. input overflow */+            /* note: if there's overflow, this should be reported+             * all the way through to client. Since input from all+             * devices is merged, we need to find all input devices+             * and set all to the overflow state.+             * NOTE: this assumes every input is ALSA based.+             */+            int rslt = snd_seq_event_input(seq, &ev);+            if (rslt >= 0) {+                handle_event(ev);+            } else if (rslt == -ENOSPC) {+                int i;+                for (i = 0; i < pm_descriptor_len; i++) {+                    if (pm_descriptors[i].pub.input) {+                        PmInternal *midi_i = pm_descriptors[i].pm_internal;+                        /* careful, device may not be open! */+                        if (midi_i) Pm_SetOverflow(midi_i->queue);+                    }+                }+            }+        }+    }+    return pmNoError;+}+++static unsigned int alsa_check_host_error(PmInternal *midi)+{+    return FALSE;+}+++pm_fns_node pm_linuxalsa_in_dictionary = {+    none_write_short,+    none_sysex,+    none_sysex,+    none_write_byte,+    none_write_short,+    none_write_flush,+    alsa_synchronize,+    alsa_in_open,+    alsa_abort,+    alsa_in_close,+    alsa_poll,+    alsa_check_host_error+};++pm_fns_node pm_linuxalsa_out_dictionary = {+    alsa_write_short,+    alsa_sysex,+    alsa_sysex,+    alsa_write_byte,+    alsa_write_short, /* short realtime message */+    alsa_write_flush,+    alsa_synchronize,+    alsa_out_open, +    alsa_abort, +    alsa_out_close,+    none_poll,+    alsa_check_host_error+};+++/* pm_strdup -- copy a string to the heap. Use this rather than strdup so + *    that we call pm_alloc, not malloc. This allows portmidi to avoid + *    malloc which might cause priority inversion. Probably ALSA is going+ *    to call malloc anyway, so this extra work here may be pointless.+ */+char *pm_strdup(const char *s)+{+    int len = strlen(s);+    char *dup = (char *) pm_alloc(len + 1);+    strcpy(dup, s);+    return dup;+}+++PmError pm_linuxalsa_init(void)+{+    int  err;+    snd_seq_client_info_t *cinfo;+    snd_seq_port_info_t *pinfo;+    unsigned int caps;++    /* Register interface ALSA with create_virtual fn */+    pm_add_interf("ALSA", &alsa_create_virtual, &alsa_delete_virtual);++    /* Previously, the last parameter was SND_SEQ_NONBLOCK, but this +     * would cause messages to be dropped if the ALSA buffer fills up.+     * The correct behavior is for writes to block until there is +     * room to send all the data. The client should normally allocate+     * a large enough buffer to avoid blocking on output. +     * Now that blocking is enabled, the seq_event_input() will block+     * if there is no input data. This is not what we want, so must+     * call seq_event_input_pending() to avoid blocking.+     */+    err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);+    if (err < 0) goto error_return;+    +    snd_seq_client_info_alloca(&cinfo);+    snd_seq_port_info_alloca(&pinfo);++    snd_seq_client_info_set_client(cinfo, -1);+    while (snd_seq_query_next_client(seq, cinfo) == 0) {+        snd_seq_port_info_set_client(pinfo,+                                     snd_seq_client_info_get_client(cinfo));+        snd_seq_port_info_set_port(pinfo, -1);+        while (snd_seq_query_next_port(seq, pinfo) == 0) {+            if (snd_seq_port_info_get_client(pinfo) == SND_SEQ_CLIENT_SYSTEM)+                continue; /* ignore Timer and Announce ports on client 0 */+            caps = snd_seq_port_info_get_capability(pinfo);+            if (!(caps & (SND_SEQ_PORT_CAP_SUBS_READ |+                          SND_SEQ_PORT_CAP_SUBS_WRITE)))+                continue; /* ignore if you cannot read or write port */+            if (caps & SND_SEQ_PORT_CAP_SUBS_WRITE) {+                if (pm_default_output_device_id == -1) +                    pm_default_output_device_id = pm_descriptor_len;+                pm_add_device("ALSA",+                        pm_strdup(snd_seq_port_info_get_name(pinfo)),+                        FALSE, FALSE,+                        MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),+                                        snd_seq_port_info_get_port(pinfo)),+                        &pm_linuxalsa_out_dictionary);+            }+            if (caps & SND_SEQ_PORT_CAP_SUBS_READ) {+                if (pm_default_input_device_id == -1) +                    pm_default_input_device_id = pm_descriptor_len;+                pm_add_device("ALSA",+                        pm_strdup(snd_seq_port_info_get_name(pinfo)),+                        TRUE, FALSE,+                        MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),+                                        snd_seq_port_info_get_port(pinfo)),+                        &pm_linuxalsa_in_dictionary);+            }+        }+    }+    return pmNoError;+ error_return:+    pm_linuxalsa_term();  /* clean up */+    return check_hosterror(err);+}+++void pm_linuxalsa_term(void)+{+    if (seq) {+        snd_seq_close(seq);+        pm_free(pm_descriptors);+        pm_descriptors = NULL;+        pm_descriptor_len = 0;+        pm_descriptor_max = 0;+    }+}++#endif
+ portmidi/pm_linux/pmlinuxnull.c view
@@ -0,0 +1,31 @@+/*+ * pmlinuxnull.c -- system specific definitions+ * + * written by:+ *  Roger Dannenberg+ * + * If there is no ALSA, you can define PMNULL and build PortMidi. It will+ * not report any devices, so you will not be able to open any, but if+ * you wanted to disable MIDI from some application, this could be used.+ *    Mainly, this code shows the possibility of supporting multiple+ * interfaces, e.g., ALSA and Sndio on BSD, or ALSA and Jack on Linux.+ * But as of Dec, 2021, the only supported MIDI API for Linux is ALSA.+ */++#ifdef PMNULL++#include "portmidi.h"+#include "pmlinuxnull.h"+++PmError pm_linuxnull_init(void)+{+    return pmNoError;+}+++void pm_linuxnull_term(void)+{+}++#endif
+ portmidi/pm_linux/pmlinuxnull.h view
@@ -0,0 +1,6 @@+/* pmlinuxnull.h -- system-specific definitions */++PmError pm_linuxnull_init(void);+void pm_linuxnull_term(void);++
portmidi/pm_mac/pmmac.c view
@@ -1,42 +1,44 @@-/* pmmac.c -- PortMidi os-dependent code */
-
-/* This file only needs to implement:
-pm_init(), which calls various routines to register the 
-available midi devices,
-Pm_GetDefaultInputDeviceID(), and
-Pm_GetDefaultOutputDeviceID().
-It is seperate from pmmacosxcm because we might want to register
-non-CoreMIDI devices.
-*/
-
-#include "stdlib.h"
-#include "portmidi.h"
-#include "pmmacosxcm.h"
-
-PmError pm_init()
-{
-    return pm_macosxcm_init();
-}
-
-void pm_term(void)
-{
-    pm_macosxcm_term();
-}
-
-PmDeviceID pm_default_input_device_id = -1;
-PmDeviceID pm_default_output_device_id = -1;
-
-PmDeviceID Pm_GetDefaultInputDeviceID()
-{
-    return pm_default_input_device_id;
-}
-
-PmDeviceID Pm_GetDefaultOutputDeviceID() {
-    return pm_default_output_device_id;
-}
-
-void *pm_alloc(size_t s) { return malloc(s); }
-
-void pm_free(void *ptr) { free(ptr); }
-
-
+/* pmmac.c -- PortMidi os-dependent code */++/* This file only needs to implement:+pm_init(), which calls various routines to register the +available midi devices,+Pm_GetDefaultInputDeviceID(), and+Pm_GetDefaultOutputDeviceID().+It is seperate from pmmacosxcm because we might want to register+non-CoreMIDI devices.+*/++#include "stdlib.h"+#include "portmidi.h"+#include "pmutil.h"+#include "pminternal.h"+#include "pmmacosxcm.h"++void pm_init(void)+{+    pm_macosxcm_init();+}+++void pm_term(void)+{+    pm_macosxcm_term();+}++PmDeviceID Pm_GetDefaultInputDeviceID(void) +{+    Pm_Initialize();+    return pm_default_input_device_id;+}++PmDeviceID Pm_GetDefaultOutputDeviceID(void) {+    Pm_Initialize();+    return pm_default_output_device_id;+}++void *pm_alloc(size_t s) { return malloc(s); }++void pm_free(void *ptr) { free(ptr); }++
− portmidi/pm_mac/pmmac.h
@@ -1,4 +0,0 @@-/* pmmac.h */
-
-extern PmDeviceID pm_default_input_device_id;
-extern PmDeviceID pm_default_output_device_id;
portmidi/pm_mac/pmmacosxcm.c view
@@ -9,927 +9,1188 @@  */   /* Notes:-    since the input and output streams are represented by MIDIEndpointRef-    values and almost no other state, we store the MIDIEndpointRef on-    descriptors[midi->device_id].descriptor. The only other state we need-    is for errors: we need to know if there is an error and if so, what is-    the error text. We use a structure with two kinds of-    host error: "error" and "callback_error". That way, asynchronous callbacks-    do not interfere with other error information.-    -    OS X does not seem to have an error-code-to-text function, so we will-    just use text messages instead of error codes.- */--#include <stdlib.h>--//#define CM_DEBUG 1--#include "portmidi.h"-#include "pmutil.h"-#include "pminternal.h"-#include "porttime.h"-#include "pmmac.h"-#include "pmmacosxcm.h"--#include <stdio.h>-#include <string.h>--#include <CoreServices/CoreServices.h>-#include <CoreMIDI/MIDIServices.h>-#include <CoreAudio/HostTime.h>--#define PACKET_BUFFER_SIZE 1024--/* this is very strange: if I put in a reasonable -   number here, e.g. 128, which would allow sysex data-   to be sent 128 bytes at a time, then I lose sysex-   data in my loopback test. With a buffer size of 4,-   we put at most 4 bytes in a packet (but maybe many-   packets in a packetList), and everything works fine.- */-#define SYSEX_BUFFER_SIZE 4--#define VERBOSE_ON 1-#define VERBOSE if (VERBOSE_ON)--#define MIDI_SYSEX      0xf0-#define MIDI_EOX        0xf7-#define MIDI_STATUS_MASK 0x80--static MIDIClientRef	client = NULL;	/* Client handle to the MIDI server */-static MIDIPortRef	portIn = NULL;	/* Input port handle */-static MIDIPortRef	portOut = NULL;	/* Output port handle */--extern pm_fns_node pm_macosx_in_dictionary;-extern pm_fns_node pm_macosx_out_dictionary;--typedef struct midi_macosxcm_struct {-    unsigned long sync_time; /* when did we last determine delta? */-    UInt64 delta;	/* difference between stream time and real time in ns */-    UInt64 last_time;	/* last output time */-    int first_message;  /* tells midi_write to sychronize timestamps */-    int sysex_mode;     /* middle of sending sysex */-    unsigned long sysex_word; /* accumulate data when receiving sysex */-    unsigned int sysex_byte_count; /* count how many received */-    char error[PM_HOST_ERROR_MSG_LEN];-    char callback_error[PM_HOST_ERROR_MSG_LEN];-    Byte packetBuffer[PACKET_BUFFER_SIZE];-    MIDIPacketList *packetList; /* a pointer to packetBuffer */-    MIDIPacket *packet;-    Byte sysex_buffer[SYSEX_BUFFER_SIZE]; /* temp storage for sysex data */-    MIDITimeStamp sysex_timestamp; /* timestamp to use with sysex data */-    /* allow for running status (is running status possible here? -rbd): -cpr */-    unsigned char last_command; -    long last_msg_length;-} midi_macosxcm_node, *midi_macosxcm_type;--/* private function declarations */-MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp);-PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp);--char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint);---static int-midi_length(long msg)-{-    int status, high, low;-    static int high_lengths[] = {-        1, 1, 1, 1, 1, 1, 1, 1,         /* 0x00 through 0x70 */-        3, 3, 3, 3, 2, 2, 3, 1          /* 0x80 through 0xf0 */-    };-    static int low_lengths[] = {-        1, 2, 3, 2, 1, 1, 1, 1,         /* 0xf0 through 0xf8 */-        1, 1, 1, 1, 1, 1, 1, 1          /* 0xf9 through 0xff */-    };--    status = msg & 0xFF;-    high = status >> 4;-    low = status & 15;--    return (high != 0xF) ? high_lengths[high] : low_lengths[low];-}--static PmTimestamp midi_synchronize(PmInternal *midi)-{-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;-    UInt64 pm_stream_time_2 = -            AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());-    PmTimestamp real_time;-    UInt64 pm_stream_time;-    /* if latency is zero and this is an output, there is no -       time reference and midi_synchronize should never be called */-    assert(midi->time_proc);-    assert(!(midi->write_flag && midi->latency == 0));-    do {-         /* read real_time between two reads of stream time */-         pm_stream_time = pm_stream_time_2;-         real_time = (*midi->time_proc)(midi->time_info);-         pm_stream_time_2 = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());-         /* repeat if more than 0.5 ms has elapsed */-    } while (pm_stream_time_2 > pm_stream_time + 500000);-    m->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000);-    m->sync_time = real_time;-    return real_time;-}---static void-process_packet(MIDIPacket *packet, PmEvent *event, -	       PmInternal *midi, midi_macosxcm_type m)-{-    /* handle a packet of MIDI messages from CoreMIDI */-    /* there may be multiple short messages in one packet (!) */-    unsigned int remaining_length = packet->length;-    unsigned char *cur_packet_data = packet->data;-    while (remaining_length > 0) {-        if (cur_packet_data[0] == MIDI_SYSEX ||-            /* are we in the middle of a sysex message? */-            (m->last_command == 0 &&-             !(cur_packet_data[0] & MIDI_STATUS_MASK))) {-            m->last_command = 0; /* no running status */-            unsigned int amt = pm_read_bytes(midi, cur_packet_data, -                                             remaining_length, -                                             event->timestamp);-            remaining_length -= amt;-            cur_packet_data += amt;-        } else if (cur_packet_data[0] == MIDI_EOX) {-            /* this should never happen, because pm_read_bytes should-             * get and read all EOX bytes*/-            midi->sysex_in_progress = FALSE;-            m->last_command = 0;-        } else if (cur_packet_data[0] & MIDI_STATUS_MASK) {-            /* compute the length of the next (short) msg in packet */-	    unsigned int cur_message_length = midi_length(cur_packet_data[0]);-            if (cur_message_length > remaining_length) {-#ifdef DEBUG-                printf("PortMidi debug msg: not enough data");-#endif-		/* since there's no more data, we're done */-		return;-	    }-	    m->last_msg_length = cur_message_length;-	    m->last_command = cur_packet_data[0];-	    switch (cur_message_length) {-	    case 1:-	        event->message = Pm_Message(cur_packet_data[0], 0, 0);-		break; -	    case 2:-	        event->message = Pm_Message(cur_packet_data[0], -					    cur_packet_data[1], 0);-		break;-	    case 3:-	        event->message = Pm_Message(cur_packet_data[0],-					    cur_packet_data[1], -					    cur_packet_data[2]);-		break;-	    default:-                /* PortMIDI internal error; should never happen */-                assert(cur_message_length == 1);-	        return; /* give up on packet if continued after assert */-	    }-	    pm_read_short(midi, event);-	    remaining_length -= m->last_msg_length;-	    cur_packet_data += m->last_msg_length;-	} else if (m->last_msg_length > remaining_length + 1) {-	    /* we have running status, but not enough data */-#ifdef DEBUG-	    printf("PortMidi debug msg: not enough data in CoreMIDI packet");-#endif-	    /* since there's no more data, we're done */-	    return;-	} else { /* output message using running status */-	    switch (m->last_msg_length) {-	    case 1:-	        event->message = Pm_Message(m->last_command, 0, 0);-		break;-	    case 2:-	        event->message = Pm_Message(m->last_command, -					    cur_packet_data[0], 0);-		break;-	    case 3:-	        event->message = Pm_Message(m->last_command, -					    cur_packet_data[0], -					    cur_packet_data[1]);-		break;-	    default:-	        /* last_msg_length is invalid -- internal PortMIDI error */-	        assert(m->last_msg_length == 1);-	    }-	    pm_read_short(midi, event);-	    remaining_length -= (m->last_msg_length - 1);-	    cur_packet_data += (m->last_msg_length - 1);-	}-    }-}----/* called when MIDI packets are received */-static void-readProc(const MIDIPacketList *newPackets, void *refCon, void *connRefCon)-{-    PmInternal *midi;-    midi_macosxcm_type m;-    PmEvent event;-    MIDIPacket *packet;-    unsigned int packetIndex;-    unsigned long now;-    unsigned int status;-    -#ifdef CM_DEBUG-    printf("readProc: numPackets %d: ", newPackets->numPackets);-#endif--    /* Retrieve the context for this connection */-    midi = (PmInternal *) connRefCon;-    m = (midi_macosxcm_type) midi->descriptor;-    assert(m);-    -    /* synchronize time references every 100ms */-    now = (*midi->time_proc)(midi->time_info);-    if (m->first_message || m->sync_time + 100 /*ms*/ < now) { -        /* time to resync */-        now = midi_synchronize(midi);-        m->first_message = FALSE;-    }-    -    packet = (MIDIPacket *) &newPackets->packet[0];-    /* printf("readproc packet status %x length %d\n", packet->data[0], -               packet->length); */-    for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) {-        /* Set the timestamp and dispatch this message */-        event.timestamp = -                (AudioConvertHostTimeToNanos(packet->timeStamp) - m->delta) / -                (UInt64) 1000000;-        status = packet->data[0];-        /* process packet as sysex data if it begins with MIDI_SYSEX, or-           MIDI_EOX or non-status byte with no running status */-#ifdef CM_DEBUG-        printf(" %d", packet->length);-#endif-        if (status == MIDI_SYSEX || status == MIDI_EOX || -            ((!(status & MIDI_STATUS_MASK)) && !m->last_command)) {-	    /* previously was: !(status & MIDI_STATUS_MASK)) {-             * but this could mistake running status for sysex data-             */-            /* reset running status data -cpr */-	    m->last_command = 0;-	    m->last_msg_length = 0;-            /* printf("sysex packet length: %d\n", packet->length); */-            pm_read_bytes(midi, packet->data, packet->length, event.timestamp);-        } else {-            process_packet(packet, &event, midi, m);-	}-        packet = MIDIPacketNext(packet);-    }-#ifdef CM_DEBUG-    printf("\n");-#endif-}--static PmError-midi_in_open(PmInternal *midi, void *driverInfo)-{-    MIDIEndpointRef endpoint;-    midi_macosxcm_type m;-    OSStatus macHostError;-    -    /* insure that we have a time_proc for timing */-    if (midi->time_proc == NULL) {-        if (!Pt_Started()) -            Pt_Start(1, 0, 0);-        /* time_get does not take a parameter, so coerce */-        midi->time_proc = (PmTimeProcPtr) Pt_Time;-    }-    -    endpoint = (MIDIEndpointRef) descriptors[midi->device_id].descriptor;-    if (endpoint == NULL) {-        return pmInvalidDeviceId;-    }--    m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */-    midi->descriptor = m;-    if (!m) {-        return pmInsufficientMemory;-    }-    m->error[0] = 0;-    m->callback_error[0] = 0;-    m->sync_time = 0;-    m->delta = 0;-    m->last_time = 0;-    m->first_message = TRUE;-    m->sysex_mode = FALSE;-    m->sysex_word = 0;-    m->sysex_byte_count = 0;-    m->packetList = NULL;-    m->packet = NULL;-    m->last_command = 0;-    m->last_msg_length = 0;--    macHostError = MIDIPortConnectSource(portIn, endpoint, midi);-    if (macHostError != noErr) {-        pm_hosterror = macHostError;-        sprintf(pm_hosterror_text, -                "Host error %ld: MIDIPortConnectSource() in midi_in_open()",-                macHostError);-        midi->descriptor = NULL;-        pm_free(m);-        return pmHostError;-    }-    -    return pmNoError;-}--static PmError-midi_in_close(PmInternal *midi)-{-    MIDIEndpointRef endpoint;-    OSStatus macHostError;-    PmError err = pmNoError;-    -    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;-    -    if (!m) return pmBadPtr;--    endpoint = (MIDIEndpointRef) descriptors[midi->device_id].descriptor;-    if (endpoint == NULL) {-        pm_hosterror = pmBadPtr;-    }-    -    /* shut off the incoming messages before freeing data structures */-    macHostError = MIDIPortDisconnectSource(portIn, endpoint);-    if (macHostError != noErr) {-        pm_hosterror = macHostError;-        sprintf(pm_hosterror_text, -                "Host error %ld: MIDIPortDisconnectSource() in midi_in_close()",-                macHostError);-        err = pmHostError;-    }-    -    midi->descriptor = NULL;-    pm_free(midi->descriptor);-    -    return err;-}---static PmError-midi_out_open(PmInternal *midi, void *driverInfo)-{-    midi_macosxcm_type m;--    m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */-    midi->descriptor = m;-    if (!m) {-        return pmInsufficientMemory;-    }-    m->error[0] = 0;-    m->callback_error[0] = 0;-    m->sync_time = 0;-    m->delta = 0;-    m->last_time = 0;-    m->first_message = TRUE;-    m->sysex_mode = FALSE;-    m->sysex_word = 0;-    m->sysex_byte_count = 0;-    m->packetList = (MIDIPacketList *) m->packetBuffer;-    m->packet = NULL;-    m->last_command = 0;-    m->last_msg_length = 0;--    return pmNoError;-}---static PmError-midi_out_close(PmInternal *midi)-{-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;-    if (!m) return pmBadPtr;-    -    midi->descriptor = NULL;-    pm_free(midi->descriptor);-    -    return pmNoError;-}--static PmError-midi_abort(PmInternal *midi)-{-    return pmNoError;-}---static PmError-midi_write_flush(PmInternal *midi, PmTimestamp timestamp)-{-    OSStatus macHostError;-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;-    MIDIEndpointRef endpoint = -            (MIDIEndpointRef) descriptors[midi->device_id].descriptor;-    assert(m);-    assert(endpoint);-    if (m->packet != NULL) {-        /* out of space, send the buffer and start refilling it */-        macHostError = MIDISend(portOut, endpoint, m->packetList);-        m->packet = NULL; /* indicate no data in packetList now */-        if (macHostError != noErr) goto send_packet_error;-    }-    return pmNoError;-    -send_packet_error:-    pm_hosterror = macHostError;-    sprintf(pm_hosterror_text, -            "Host error %ld: MIDISend() in midi_write()",-            macHostError);-    return pmHostError;--}---static PmError-send_packet(PmInternal *midi, Byte *message, unsigned int messageLength, -            MIDITimeStamp timestamp)-{-    PmError err;-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;-    assert(m);-    -    /* printf("add %d to packet %lx len %d\n", message[0], m->packet, messageLength); */-    m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer), -                                  m->packet, timestamp, messageLength, -                                  message);-    if (m->packet == NULL) {-        /* out of space, send the buffer and start refilling it */-        /* make midi->packet non-null to fool midi_write_flush into sending */-        m->packet = (MIDIPacket *) 4; -        if ((err = midi_write_flush(midi, timestamp)) != pmNoError) return err;-        m->packet = MIDIPacketListInit(m->packetList);-        assert(m->packet); /* if this fails, it's a programming error */-        m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer),-                                      m->packet, timestamp, messageLength, -                                      message);-        assert(m->packet); /* can't run out of space on first message */           -    }-    return pmNoError;-}    ---static PmError-midi_write_short(PmInternal *midi, PmEvent *event)-{-    long when = event->timestamp;-    long what = event->message;-    MIDITimeStamp timestamp;-    UInt64 when_ns;-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;-    Byte message[4];-    unsigned int messageLength;--    if (m->packet == NULL) {-        m->packet = MIDIPacketListInit(m->packetList);-        /* this can never fail, right? failure would indicate something -           unrecoverable */-        assert(m->packet);-    }-    -    /* compute timestamp */-    if (when == 0) when = midi->now;-    /* if latency == 0, midi->now is not valid. We will just set it to zero */-    if (midi->latency == 0) when = 0;-    when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta;-    /* make sure we don't go backward in time */-    if (when_ns < m->last_time) when_ns = m->last_time;-    m->last_time = when_ns;-    timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);--    message[0] = Pm_MessageStatus(what);-    message[1] = Pm_MessageData1(what);-    message[2] = Pm_MessageData2(what);-    messageLength = midi_length(what);-        -    /* Add this message to the packet list */-    return send_packet(midi, message, messageLength, timestamp);-}---static PmError -midi_begin_sysex(PmInternal *midi, PmTimestamp when)-{-    UInt64 when_ns;-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;-    assert(m);-    m->sysex_byte_count = 0;-    -    /* compute timestamp */-    if (when == 0) when = midi->now;-    /* if latency == 0, midi->now is not valid. We will just set it to zero */-    if (midi->latency == 0) when = 0;-    when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta;-    m->sysex_timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);--    if (m->packet == NULL) {-        m->packet = MIDIPacketListInit(m->packetList);-        /* this can never fail, right? failure would indicate something -           unrecoverable */-        assert(m->packet);-    }-    return pmNoError;-}---static PmError-midi_end_sysex(PmInternal *midi, PmTimestamp when)-{-    PmError err;-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;-    assert(m);-    -    /* make sure we don't go backward in time */-    if (m->sysex_timestamp < m->last_time) m->sysex_timestamp = m->last_time;-    -    /* now send what's in the buffer */-    err = send_packet(midi, m->sysex_buffer, m->sysex_byte_count,-                      m->sysex_timestamp);-    m->sysex_byte_count = 0;-    if (err != pmNoError) {-        m->packet = NULL; /* flush everything in the packet list */-        return err;-    }-    return pmNoError;-}---static PmError-midi_write_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp)-{-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;-    assert(m);-    if (m->sysex_byte_count >= SYSEX_BUFFER_SIZE) {-        PmError err = midi_end_sysex(midi, timestamp);-        if (err != pmNoError) return err;-    }-    m->sysex_buffer[m->sysex_byte_count++] = byte;-    return pmNoError;-}---static PmError-midi_write_realtime(PmInternal *midi, PmEvent *event)-{-    /* to send a realtime message during a sysex message, first-       flush all pending sysex bytes into packet list */-    PmError err = midi_end_sysex(midi, 0);-    if (err != pmNoError) return err;-    /* then we can just do a normal midi_write_short */-    return midi_write_short(midi, event);-}--static unsigned int midi_has_host_error(PmInternal *midi)-{-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;-    return (m->callback_error[0] != 0) || (m->error[0] != 0);-}---static void midi_get_host_error(PmInternal *midi, char *msg, unsigned int len)-{-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;-    msg[0] = 0; /* initialize to empty string */-    if (m) { /* make sure there is an open device to examine */-        if (m->error[0]) {-            strncpy(msg, m->error, len);-            m->error[0] = 0; /* clear the error */-        } else if (m->callback_error[0]) {-            strncpy(msg, m->callback_error, len);-            m->callback_error[0] = 0; /* clear the error */-        }-        msg[len - 1] = 0; /* make sure string is terminated */-    }-}---MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp)-{-    UInt64 nanos;-    if (timestamp <= 0) {-        return (MIDITimeStamp)0;-    } else {-        nanos = (UInt64)timestamp * (UInt64)1000000;-        return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos);-    }-}--PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp)-{-    UInt64 nanos;-    nanos = AudioConvertHostTimeToNanos(timestamp);-    return (PmTimestamp)(nanos / (UInt64)1000000);-}---//-// Code taken from http://developer.apple.com/qa/qa2004/qa1374.html-//////////////////////////////////////-// Obtain the name of an endpoint without regard for whether it has connections.-// The result should be released by the caller.-CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)-{-  CFMutableStringRef result = CFStringCreateMutable(NULL, 0);-  CFStringRef str;-  -  // begin with the endpoint's name-  str = NULL;-  MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str);-  if (str != NULL) {-    CFStringAppend(result, str);-    CFRelease(str);-  }-  -  MIDIEntityRef entity = NULL;-  MIDIEndpointGetEntity(endpoint, &entity);-  if (entity == NULL)-    // probably virtual-    return result;-  -  if (CFStringGetLength(result) == 0) {-    // endpoint name has zero length -- try the entity-    str = NULL;-    MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str);-    if (str != NULL) {-      CFStringAppend(result, str);-      CFRelease(str);-    }-  }-  // now consider the device's name-  MIDIDeviceRef device = NULL;-  MIDIEntityGetDevice(entity, &device);-  if (device == NULL)-    return result;-  -  str = NULL;-  MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str);-  if (CFStringGetLength(result) == 0) {-      CFRelease(result);-      return str;-  }-  if (str != NULL) {-    // if an external device has only one entity, throw away-    // the endpoint name and just use the device name-    if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) {-      CFRelease(result);-      return str;-    } else {-      if (CFStringGetLength(str) == 0) {-        CFRelease(str);-        return result;-      }-      // does the entity name already start with the device name?-      // (some drivers do this though they shouldn't)-      // if so, do not prepend-        if (CFStringCompareWithOptions( result, /* endpoint name */-             str /* device name */,-             CFRangeMake(0, CFStringGetLength(str)), 0) != kCFCompareEqualTo) {-        // prepend the device name to the entity name-        if (CFStringGetLength(result) > 0)-          CFStringInsert(result, 0, CFSTR(" "));-        CFStringInsert(result, 0, str);-      }-      CFRelease(str);-    }-  }-  return result;-}---// Obtain the name of an endpoint, following connections.-// The result should be released by the caller.-static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint)-{-  CFMutableStringRef result = CFStringCreateMutable(NULL, 0);-  CFStringRef str;-  OSStatus err;-  int i;-  -  // Does the endpoint have connections?-  CFDataRef connections = NULL;-  int nConnected = 0;-  bool anyStrings = false;-  err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, &connections);-  if (connections != NULL) {-    // It has connections, follow them-    // Concatenate the names of all connected devices-    nConnected = CFDataGetLength(connections) / sizeof(MIDIUniqueID);-    if (nConnected) {-      const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections));-      for (i = 0; i < nConnected; ++i, ++pid) {-        MIDIUniqueID id = EndianS32_BtoN(*pid);-        MIDIObjectRef connObject;-        MIDIObjectType connObjectType;-        err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType);-        if (err == noErr) {-          if (connObjectType == kMIDIObjectType_ExternalSource  ||-              connObjectType == kMIDIObjectType_ExternalDestination) {-            // Connected to an external device's endpoint (10.3 and later).-            str = EndpointName((MIDIEndpointRef)(connObject), true);-          } else {-            // Connected to an external device (10.2) (or something else, catch-all)-            str = NULL;-            MIDIObjectGetStringProperty(connObject, kMIDIPropertyName, &str);-          }-          if (str != NULL) {-            if (anyStrings)-              CFStringAppend(result, CFSTR(", "));-            else anyStrings = true;-            CFStringAppend(result, str);-            CFRelease(str);-          }-        }-      }-    }-    CFRelease(connections);-  }-  if (anyStrings)-    return result;-  -  // Here, either the endpoint had no connections, or we failed to obtain names for any of them.-  return EndpointName(endpoint, false);-}---char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint)-{-#ifdef OLDCODE-    MIDIEntityRef entity;-    MIDIDeviceRef device;--    CFStringRef endpointName = NULL;-    CFStringRef deviceName = NULL;-#endif-    CFStringRef fullName = NULL;-    CFStringEncoding defaultEncoding;-    char* newName;--    /* get the default string encoding */-    defaultEncoding = CFStringGetSystemEncoding();--    fullName = ConnectedEndpointName(endpoint);-    -#ifdef OLDCODE-    /* get the entity and device info */-    MIDIEndpointGetEntity(endpoint, &entity);-    MIDIEntityGetDevice(entity, &device);--    /* create the nicely formated name */-    MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &endpointName);-    MIDIObjectGetStringProperty(device, kMIDIPropertyName, &deviceName);-    if (deviceName != NULL) {-        fullName = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@: %@"),-                                            deviceName, endpointName);-    } else {-        fullName = endpointName;-    }-#endif    -    /* copy the string into our buffer */-    newName = (char *) malloc(CFStringGetLength(fullName) + 1);-    CFStringGetCString(fullName, newName, CFStringGetLength(fullName) + 1,-                       defaultEncoding);--    /* clean up */-#ifdef OLDCODE-    if (endpointName) CFRelease(endpointName);-    if (deviceName) CFRelease(deviceName);-#endif-    if (fullName) CFRelease(fullName);--    return newName;-}-- --pm_fns_node pm_macosx_in_dictionary = {-    none_write_short,-    none_sysex,-    none_sysex,-    none_write_byte,-    none_write_short,-    none_write_flush,-    none_synchronize,-    midi_in_open,-    midi_abort,-    midi_in_close,-    success_poll,-    midi_has_host_error,-    midi_get_host_error,-};--pm_fns_node pm_macosx_out_dictionary = {-    midi_write_short,-    midi_begin_sysex,-    midi_end_sysex,-    midi_write_byte,-    midi_write_realtime,-    midi_write_flush,-    midi_synchronize,-    midi_out_open,-    midi_abort,-    midi_out_close,-    success_poll,-    midi_has_host_error,-    midi_get_host_error,-};---PmError pm_macosxcm_init(void)-{-    ItemCount numInputs, numOutputs, numDevices;-    MIDIEndpointRef endpoint;-    int i;-    OSStatus macHostError;-    char *error_text;--    /* Determine the number of MIDI devices on the system */-    numDevices = MIDIGetNumberOfDevices();-    numInputs = MIDIGetNumberOfSources();-    numOutputs = MIDIGetNumberOfDestinations();--    /* Return prematurely if no devices exist on the system-       Note that this is not an error. There may be no devices.-       Pm_CountDevices() will return zero, which is correct and-       useful information-     */-    if (numDevices <= 0) {-        return pmNoError;-    }---    /* Initialize the client handle */-    macHostError = MIDIClientCreate(CFSTR("PortMidi"), NULL, NULL, &client);-    if (macHostError != noErr) {-        error_text = "MIDIClientCreate() in pm_macosxcm_init()";-        goto error_return;-    }--    /* Create the input port */-    macHostError = MIDIInputPortCreate(client, CFSTR("Input port"), readProc,-                                          NULL, &portIn);-    if (macHostError != noErr) {-        error_text = "MIDIInputPortCreate() in pm_macosxcm_init()";-        goto error_return;-    }-        -    /* Create the output port */-    macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut);-    if (macHostError != noErr) {-        error_text = "MIDIOutputPortCreate() in pm_macosxcm_init()";-        goto error_return;-    }--    /* Iterate over the MIDI input devices */-    for (i = 0; i < numInputs; i++) {-        endpoint = MIDIGetSource(i);-        if (endpoint == NULL) {-            continue;-        }--        /* set the first input we see to the default */-        if (pm_default_input_device_id == -1)-            pm_default_input_device_id = pm_descriptor_index;-        -        /* Register this device with PortMidi */-        pm_add_device("CoreMIDI", cm_get_full_endpoint_name(endpoint),-                      TRUE, (void*)endpoint, &pm_macosx_in_dictionary);-    }--    /* Iterate over the MIDI output devices */-    for (i = 0; i < numOutputs; i++) {-        endpoint = MIDIGetDestination(i);-        if (endpoint == NULL) {-            continue;-        }--        /* set the first output we see to the default */-        if (pm_default_output_device_id == -1)-            pm_default_output_device_id = pm_descriptor_index;--        /* Register this device with PortMidi */-        pm_add_device("CoreMIDI", cm_get_full_endpoint_name(endpoint),-                      FALSE, (void*)endpoint, &pm_macosx_out_dictionary);-    }-    return pmNoError;-    -error_return:-    pm_hosterror = macHostError;-    sprintf(pm_hosterror_text, "Host error %ld: %s\n", macHostError, error_text);-    pm_macosxcm_term(); /* clear out any opened ports */-    return pmHostError;-}--void pm_macosxcm_term(void)-{-    if (client != NULL)	 MIDIClientDispose(client);-    if (portIn != NULL)	 MIDIPortDispose(portIn);-    if (portOut != NULL) MIDIPortDispose(portOut);++    Since the input and output streams are represented by+    MIDIEndpointRef values and almost no other state, we store the+    MIDIEndpointRef on pm_descriptors[midi->device_id].descriptor.+    +    OS X does not seem to have an error-code-to-text function, so we+    will just use text messages instead of error codes.++    Virtual device input synchronization: Once we create a virtual+    device, it is always "on" and receiving messages, but it must drop+    messages unless the device has been opened with Pm_OpenInput. To+    open, the main thread should create all the data structures, then+    call OSMemoryBarrier so that writes are observed, then set+    is_opened = TRUE.  To close without locks, we need to get the+    callback to set is_opened to FALSE before we free data structures;+    otherwise, there's a race condition where closing could delete+    structures in use by the virtual_read_callback function. We send+    8 MIDI resets (FF) in a single packet to our own port to signal+    the virtual_read_callback to close it. Then, we wait for the+    callback to recognize the "close" packet and reset is_opened.+ +    Device scanning is done when you first open an application.+    PortMIDI does not actively update the devices. Instead, you must+    Pm_Terminate() and Pm_Initialize(), basically starting over. But+    CoreMIDI does not have a way to shut down(!), and even+    MIDIClientDispose() somehow retains state (and docs say do not+    call it even if it worked). The solution, apparently, is to+    call CFRunLoopRunInMode(), which somehow updates CoreMIDI+    state.+ +    But when do we call CFRunLoopRunInMode()? I tried calling it+    in midi_in_poll() which is called when you call Pm_Read() since+    that is called often. I observed that this caused the program+    to block for as long as 50ms and fairly often for 2 or 3ms.+    What was Apple thinking? Is it really OK to design systems that+    can only function with a tricky multi-threaded, non-blocking+    priority-based solution, and then not provide a proof of concept+    or documentation? Or is Apple's design really flawed? If anyone+    at Apple reads this, please let me know -- I'm curious.+ +    But I digress... Here's the PortMidi approach: Since+    CFRunLoopRunInMode() is potentially a non-realtime operation,+    we only call it in Pm_Initialize(), where other calls to look+    up devices and device names are quite slow to begin with. Again,+    PortMidi does not actively scan for new or deleted devices, so+    if devices change, you won't see it until the next Pm_Terminate+    and Pm_Initialize.+ +    Calling CFRunLoopRunInMode() once is probably not enough. There+    might be better way, but it seems to work to just call it 100+    times and insert 20 1ms delays (in case some inter-process+    communication or synchronization is going on).+    This adds 20ms to the wall time of Pm_Initialize(), but it+    typically runs 30ms to much more (~4s), so this has little impact.+ */++#include <stdlib.h>++/* turn on lots of debugging print statements */+#define CM_DEBUG if (0)+/* #define CM_DEBUG if (1) */++#include "portmidi.h"+#include "pmutil.h"+#include "pminternal.h"+#include "porttime.h"+#include "pmmacosxcm.h"++#include <stdio.h>+#include <string.h>++#include <CoreServices/CoreServices.h>+#include <CoreMIDI/MIDIServices.h>+#include <CoreAudio/HostTime.h>+#include <unistd.h>+#include <libkern/OSAtomic.h>++#define PACKET_BUFFER_SIZE 1024+/* maximum overall data rate (OS X limits MIDI rate in case there+ * is a cycle among IAC ports.+ */++#define MAX_BYTES_PER_S 5400++/* Apple reports that packets are dropped when the MIDI bytes/sec+   exceeds 15000. This is computed by "tracking the number of MIDI+   bytes scheduled into 1-second buckets over the last six seconds and+   averaging these counts." This was confirmed in measurements+   (2021) with pm_test/fast.c and pm_test/fastrcv.c Now, in 2022, with+   macOS 12, pm_test/fast{rcv}.c show problems begin at 6000 bytes/sec.+   Previously, we set MAX_BYTES_PER_S to 14000. This is reduced to+   5400 based on testing (which shows 5700 is too high) to fix the+   packet loss problem that showed up with macOS 12.+ +   Experiments show this restriction applies to IAC bus MIDI, but not+   to hardware interfaces. (I measured 0.5 Mbps each way over USB to a+   Teensy 3.2 microcontroller implementing a USB MIDI loopback. Maybe+   it would get 1 Mbps one-way, which would make the CoreMIDI+   restriction 18x slower than USB. Maybe other USB MIDI+   implementations are faster -- USB top speed for other protocols is+   certainly higher than 1 Mbps!)++   This is apparently based on timestamps, not on real time, so we+   have to avoid constructing packets that schedule high speed output+   regardless of when writes occur. The solution is to alter+   timestamps to limit data rates.  This adds a slight time+   distortion, e.g. an 11 note chord with all notes on the same+   timestamp will be altered so that the last message is delayed by+   11 messages x 3 bytes/message / 5400 bytes/second = 6.1 ms.+   Note that this is about 2x MIDI speed, but at least 18x slower +   than USB MIDI.+ +   Altering timestamps creates another problem, which is that a sender+   that exceeds the maximum rate can queue up an unbounded number of+   messages. With non-USB MIDI devices, you could be writing 5x faster+   to CoreMIDI than the hardware interface can send, causing an+   unbounded backlog, not to mention that the output stream will be a+   steady byte stream (e.g., one 3-byte MIDI message every 0.55 ms),+   losing any original timing or rhythm. PortMidi does not guarantee+   delivery if, over the long run, you write faster than the hardware+   can send.+   +   The LIMIT_RATE symbol, if defined (which is the default), enables+   code to modify timestamps for output to an IAC device as follows:++   Before a packet is formed, the message timestamp is set to the+   maximum of the PortMidi timestamp (converted to CoreMIDI time)+   and min_next_time. After each send, min_next_time is updated to+   the packet time + packet length * delay_per_byte, which limits+   the scheduled bytes-per-second. Also, after each packet list+   flush, min_next_time is updated to the maximum of min_next_time+   and the real time, which prevents many bytes to be scheduled in+   the past. (We could more directly just say packets are never+   scheduled in the past, but we prefer to get the current time -- a+   system call -- only when we perform the more expensive operation+   of flushing packets, so that's when we update min_next_time to+   the current real time. If we are sending a lot, we have to flush+   a lot, so the time will be updated frequently when it matters.)++   This possible adjustment to timestamps can distort accurate+   timestamps by up to 0.556 us per 3-byte MIDI message.+ +   Nothing blocks the sender from queueing up an arbitrary number of+   messages. Timestamps should be used for accurate timing by sending+   timestamped messages a little ahead of real time, not for+   scheduling an entire MIDI sequence at once!+ */+#define LIMIT_RATE 1++#define SYSEX_BUFFER_SIZE 128+/* What is the maximum PortMidi device number for an IAC device? A+ * cleaner design would be to not use the endpoint as our device+ * representation. Instead, we could have a private extensible struct+ * to keep all device information, including whether the device is+ * implemented with the AppleMIDIIACDriver, which we need because we+ * have to limit the data rate to this particular driver to avoid+ * dropping messages. Rather than rewrite a lot of code, I am just+ * allocating 64 bytes to flag which devices are IAC ones. If an IAC+ * device number is greater than 63, PortMidi will fail to limit+ * writes to it, but will not complain and will not access memory+ * outside the 64-element array of char.+ */+#define MAX_IAC_NUM 63++#define VERBOSE_ON 1+#define VERBOSE if (VERBOSE_ON)++#define MIDI_SYSEX       0xf0+#define MIDI_EOX         0xf7+#define MIDI_CLOCK       0xf8+#define MIDI_STATUS_MASK 0x80++/* "Ref"s are pointers on 32-bit machines and ints on 64 bit machines+   NULL_REF is our representation of either 0 or NULL+*/+#ifdef __LP64__+#define NULL_REF 0+#else+#define NULL_REF NULL+#endif++static MIDIClientRef client = NULL_REF;  /* Client handle to the MIDI server */+static MIDIPortRef   portIn = NULL_REF;  /* Input port handle */+static MIDIPortRef   portOut = NULL_REF;  /* Output port handle */+static char          isIAC[MAX_IAC_NUM + 1];  /* is device an IAC device */++extern pm_fns_node pm_macosx_in_dictionary;+extern pm_fns_node pm_macosx_out_dictionary;++typedef struct coremidi_info_struct {+    int is_virtual;     /* virtual device (TRUE) or actual device (FALSE)? */+    UInt64 delta;	/* difference between stream time and real time in ns */+    int sysex_mode;     /* middle of sending sysex */+    uint32_t sysex_word; /* accumulate data when receiving sysex */+    uint32_t sysex_byte_count; /* count how many received */+    char error[PM_HOST_ERROR_MSG_LEN];+    char callback_error[PM_HOST_ERROR_MSG_LEN];+    Byte packetBuffer[PACKET_BUFFER_SIZE];+    MIDIPacketList *packetList; /* a pointer to packetBuffer */+    MIDIPacket *packet;+    Byte sysex_buffer[SYSEX_BUFFER_SIZE]; /* temp storage for sysex data */+    MIDITimeStamp sysex_timestamp; /* host timestamp to use with sysex data */+    /* allow for running status (is running status possible here? -rbd): -cpr */+    UInt64 min_next_time; /* when can the next send take place? (host time) */+    int isIACdevice;+    Float64 us_per_host_tick; /* host clock frequency, units of min_next_time */+    UInt64 host_ticks_per_byte; /* host clock units per byte at maximum rate */+} coremidi_info_node, *coremidi_info_type;++/* private function declarations */+MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp); /* returns host time */+PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp); /* returns ms */++char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag);++static PmError check_hosterror(OSStatus err, const char *msg)+{+    if (err != noErr) {+        snprintf(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, "Host error %ld: %s", (long) err, msg);+        pm_hosterror = TRUE;+        return pmHostError;+    }+    return pmNoError;+}+++static PmTimestamp midi_synchronize(PmInternal *midi)+{+    coremidi_info_type info = (coremidi_info_type) midi->api_info;+    UInt64 pm_stream_time_2 = /* current time in ns */+            AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());+    PmTimestamp real_time;  /* in ms */+    UInt64 pm_stream_time;  /* in ns */+    /* if latency is zero and this is an output, there is no +       time reference and midi_synchronize should never be called */+    assert(midi->time_proc);+    assert(midi->is_input || midi->latency != 0);+    do {+         /* read real_time between two reads of stream time */+         pm_stream_time = pm_stream_time_2;+         real_time = (*midi->time_proc)(midi->time_info);+         pm_stream_time_2 = AudioConvertHostTimeToNanos(+                                    AudioGetCurrentHostTime());+         /* repeat if more than 0.5 ms has elapsed */+    } while (pm_stream_time_2 > pm_stream_time + 500000);+    info->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000);+    midi->sync_time = real_time;+    return real_time;+}+++/* called when MIDI packets are received */+static void read_callback(const MIDIPacketList *newPackets, PmInternal *midi)+{+    PmTimestamp timestamp;+    MIDIPacket *packet;+    unsigned int packetIndex;+    uint32_t now;+    /* Retrieve the context for this connection */+    coremidi_info_type info = (coremidi_info_type) midi->api_info;+    assert(info);+    +    CM_DEBUG printf("read_callback: numPackets %d: ", newPackets->numPackets);+    +    /* synchronize time references every 100ms */+    now = (*midi->time_proc)(midi->time_info);+    if (midi->first_message || midi->sync_time + 100 /*ms*/ < now) {+        /* time to resync */+        now = midi_synchronize(midi);+        midi->first_message = FALSE;+    }+    +    packet = (MIDIPacket *) &newPackets->packet[0];+    /* hardware devices get untimed messages and apply timestamps. We+     * want to preserve them because they should be more accurate than+     * applying the current time here. virtual devices just pass on the+     * packet->timeStamp, which could be anything. PortMidi says the +     * PortMidi timestamp is the time the message is received. We do not+     * know if we are receiving from a device driver or a virtual device.+     * PortMidi sends to virtual devices get a current timestamp, so we+     * can treat them as the receive time. If the timestamp is zero,+     * suggested by CoreMIDI as the value to use for immediate delivery,+     * then we plug in `now` which is obtained above. If another+     * application sends bogus non-zero timestamps, we will convert them+     * to this port's reference time and pass them as event.timestamp.+     * Receiver beware.+     */+    CM_DEBUG printf("read_callback packet @ %lld ns (host %lld) "+                    "status %x length %d\n",+                    AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()),+                    AudioGetCurrentHostTime(),+                    packet->data[0], packet->length);+    for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) {+        /* Set the timestamp and dispatch this message */+        CM_DEBUG printf("    packet->timeStamp %lld ns %lld host\n",+                        packet->timeStamp,+                        AudioConvertHostTimeToNanos(packet->timeStamp));+        if (packet->timeStamp == 0) {+            timestamp = now;+        } else {+            timestamp = (PmTimestamp) /* explicit conversion */ (+                (AudioConvertHostTimeToNanos(packet->timeStamp) - info->delta) /+                (UInt64) 1000000);+        }+        pm_read_bytes(midi, packet->data, packet->length, timestamp);+        packet = MIDIPacketNext(packet);+    }+}++/* callback for real devices - redirects to read_callback */+static void device_read_callback(const MIDIPacketList *newPackets, +                                 void *refCon, void *connRefCon)+{+    read_callback(newPackets, (PmInternal *) connRefCon);+}+++/* callback for virtual devices - redirects to read_callback */+static void virtual_read_callback(const MIDIPacketList *newPackets, +                                  void *refCon, void *connRefCon)+{+    /* this refCon is the device ID -- if there is a valid ID and +       the pm_descriptors table has a non-null pointer to a PmInternal,+       then then device is open and should receive this data */+    PmDeviceID id = (PmDeviceID) (size_t) refCon;+    if (id >= 0 && id < pm_descriptor_len) {+        if (pm_descriptors[id].pub.opened) {+            /* check for close request (7 reset status bytes): */+            if (newPackets->numPackets == 1 &&+                newPackets->packet[0].length == 8 &&+                /* CoreMIDI declares packets with 4-byte alignment, so we+                 * should be safe to test for 8 0xFF's as 2 32-bit values: */+                *(SInt32 *) &newPackets->packet[0].data[0] == -1 &&+                *(SInt32 *) &newPackets->packet[0].data[4] == -1) {+                CM_DEBUG printf("got close request packet\n");+                pm_descriptors[id].pub.opened = FALSE;+                return;+            } else {+                read_callback(newPackets, pm_descriptors[id].pm_internal);+            }+        }+    }+}+++/* allocate and initialize our internal coremidi connection info */+static coremidi_info_type create_macosxcm_info(int is_virtual, int is_input)+{+    coremidi_info_type info = (coremidi_info_type)+            pm_alloc(sizeof(coremidi_info_node));+    if (!info) {+        return NULL;+    }+    info->is_virtual = is_virtual;+    info->delta = 0;+    info->sysex_mode = FALSE;+    info->sysex_word = 0;+    info->sysex_byte_count = 0;+    info->packet = NULL;+    info->min_next_time = 0;+    info->isIACdevice = FALSE;+    info->us_per_host_tick = 1000000.0 / AudioGetHostClockFrequency();+    info->host_ticks_per_byte =+            (UInt64) (1000000.0 / (info->us_per_host_tick * MAX_BYTES_PER_S));+    info->packetList = (is_input ? NULL :+                                   (MIDIPacketList *) info->packetBuffer);+    return info;+}+++static PmError midi_in_open(PmInternal *midi, void *driverInfo)+{+    MIDIEndpointRef endpoint;+    coremidi_info_type info;+    OSStatus macHostError;+    int is_virtual = pm_descriptors[midi->device_id].pub.is_virtual;+    +    /* if this is an external device, descriptor is a MIDIEndpointRef.+     * if this is a virtual device for this application, descriptor is NULL.+     */+    if (!is_virtual) {+        endpoint = (MIDIEndpointRef) (intptr_t)+                   pm_descriptors[midi->device_id].descriptor;+        if (endpoint == NULL_REF) {+            return pmInvalidDeviceId;+        }+    }++    info = create_macosxcm_info(is_virtual, TRUE);+    midi->api_info = info;+    if (!info) {+        return pmInsufficientMemory;+    }+    if (!is_virtual) {+        macHostError = MIDIPortConnectSource(portIn, endpoint, midi);+        if (macHostError != noErr) {+            midi->api_info = NULL;+            pm_free(info);+            return  check_hosterror(macHostError,+                            "MIDIPortConnectSource() in midi_in_open()");+        }+    }+    return pmNoError;+}++static PmError midi_in_close(PmInternal *midi)+{+    MIDIEndpointRef endpoint;+    OSStatus macHostError;+    PmError err = pmNoError;+    +    coremidi_info_type info = (coremidi_info_type) midi->api_info;+    +    if (!info) return pmBadPtr;++    endpoint = (MIDIEndpointRef) (intptr_t)+               pm_descriptors[midi->device_id].descriptor;+    if (endpoint == NULL_REF) {+        return pmBadPtr;+    }+    +    if (!info->is_virtual) {+        /* shut off the incoming messages before freeing data structures */+        macHostError = MIDIPortDisconnectSource(portIn, endpoint);+        /* If the source closes, you get paramErr == -50 here. It seems+         * possible to monitor changes like sources closing by getting+         * notifications ALL changes, but the CoreMIDI documentation is+         * really terrible overall, and it seems easier to just ignore+         * this host error.+         */+        if (macHostError != noErr && macHostError != -50) {+            pm_hosterror = TRUE;+            err = check_hosterror(macHostError, +                          "MIDIPortDisconnectSource() in midi_in_close()");+        }+    } else {+        /* make "close virtual port" message */+        SInt64 close_port_bytes = 0xFFFFFFFFFFFFFFFF;+        /* memory requirements: packet count (4), timestamp (8), length (2),+         *     data (8). Total: 22, but we allocate plenty more:+         */+        Byte packetBuffer[64];+        MIDIPacketList *plist = (MIDIPacketList *) packetBuffer;+        MIDIPacket *packet = MIDIPacketListInit(plist);+        MIDIPacketListAdd(plist, 64, packet, 0, 8,+                          (const Byte *) &close_port_bytes);+        macHostError = MIDISend(portOut, endpoint, plist);+        if (macHostError != noErr) {+            err = check_hosterror(macHostError, "MIDISend() (PortMidi close "+                                  "port packet) in midi_in_close()");+        }            +        /* when packet is delivered, callback thread will clear opened;+         * we must wait for that before removing the input queues etc.+         * Maybe this could use signals of some kind, but if signals use+         * locks, locks can cause priority inversion problems, so we will+         * just sleep as needed. On the MIDI timescale, inserting a 0.5ms+         * latency should be OK, as the application has no business+         * opening/closing devices during time-critical moments.+         *+         * We expect the MIDI thread to close the device quickly (<0.5ms),+         * but we wait up to 50ms in case something terrible happens like+         * getting paged out in the middle of deliving packets to this+         * virtual device. If there is still no response, we time out and+         * force the close without the MIDI thread (even this will probably+         * succeed - the problem would be: this thread proceeds to delete+         * the input queues, and the freed memory is reallocated and+         * overwritten so that queues are no longer usable. Meanwhile,+         * the MIDI thread has already begun to deliver packets, so the+         * check for opened == TRUE passed, but MIDI thread does not insert+         * into queue until queue is freed, reallocated and overwritten.+         */+        for (int i = 0; i < 100; i++) {  /* up to 50ms delay */+            if (!pm_descriptors[midi->device_id].pub.opened) {+                break;+            }+            usleep(500);  /* 0.5ms */+        }+        pm_descriptors[midi->device_id].pub.opened = FALSE;  /* force it */+    }+    midi->api_info = NULL;+    pm_free(info);+    return err;+}+++static PmError midi_out_open(PmInternal *midi, void *driverInfo)+{+    coremidi_info_type info;+    int is_virtual = pm_descriptors[midi->device_id].pub.is_virtual;++    info = create_macosxcm_info(is_virtual, FALSE);+    if (midi->device_id <= MAX_IAC_NUM) {+        info->isIACdevice = isIAC[midi->device_id];+        CM_DEBUG printf("midi_out_open isIACdevice %d\n", info->isIACdevice);+    }+    midi->api_info = info;+    if (!info) {+        return pmInsufficientMemory;+    }+    return pmNoError;+}+++static PmError midi_out_close(PmInternal *midi)+{+    coremidi_info_type info = (coremidi_info_type) midi->api_info;+    if (!info) return pmBadPtr;+    midi->api_info = NULL;+    pm_free(info);+    return pmNoError;+}+++/* MIDIDestinationCreate apparently cannot create a virtual device+ * without a callback and a "refCon" parameter, but when we create+ * a virtual device, we do not want a PortMidi stream yet -- that+ * should wait for the user to open the stream. So, for the refCon,+ * use the PortMidi device ID. The callback will check if the+ * device is opened within PortMidi, and if so, use the pm_descriptors+ * table to locate the corresponding PmStream.+ */+static PmError midi_create_virtual(int is_input, const char *name,+                                   void *device_info)+{+    OSStatus macHostError;+    MIDIEndpointRef endpoint;+    CFStringRef nameRef;+    PmDeviceID id = pm_add_device("CoreMIDI", name, is_input, TRUE, NULL,+                                  (is_input ? &pm_macosx_in_dictionary :+                                              &pm_macosx_out_dictionary));+    if (id < 0) {  /* error -- out of memory or name conflict? */+        return id;+    }++    nameRef = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);+    if (is_input) {+        macHostError = MIDIDestinationCreate(client, nameRef, +                virtual_read_callback, (void *) (intptr_t) id, &endpoint);+    } else {+        macHostError = MIDISourceCreate(client, nameRef, &endpoint);+    }+    CFRelease(nameRef);++    if (macHostError != noErr) {+        /* undo the device we just allocated */+        pm_undo_add_device(id);+        return check_hosterror(macHostError, (is_input ?+                "MIDIDestinationCreateWithProtocol() in midi_create_virtual()" :+                "MIDISourceCreateWithProtocol() in midi_create_virtual()"));+    }++    /* Do we have a manufacturer name? If not, set to "PortMidi" */+    const char *mfr_name = "PortMidi";+    PmSysDepInfo *info = (PmSysDepInfo *) device_info;+    /* the version where pmKeyCoreMidiManufacturer was introduced is 210 */+    if (info && info->structVersion >= 210) {+        int i;+        for (i = 0; i < info->length; i++) {  /* search for key */+            if (info->properties[i].key == pmKeyCoreMidiManufacturer) {+                mfr_name = info->properties[i].value;+                break;+            }  /* no other keys are recognized; they are ignored */+        }+    }+    nameRef = CFStringCreateWithCString(NULL, mfr_name, kCFStringEncodingUTF8);+    MIDIObjectSetStringProperty(endpoint, kMIDIPropertyManufacturer, nameRef);+    CFRelease(nameRef);++    pm_descriptors[id].descriptor = (void *) (intptr_t) endpoint;+    return id;+}+++static PmError midi_delete_virtual(PmDeviceID id)+{+    MIDIEndpointRef endpoint;+    OSStatus macHostError;+    +    endpoint = (MIDIEndpointRef) (long) pm_descriptors[id].descriptor;+    if (endpoint == NULL_REF) {+        return pmBadPtr;+    }+    macHostError = MIDIEndpointDispose(endpoint);+    return check_hosterror(macHostError,+                           "MIDIEndpointDispose() in midi_in_close()");+}+++static PmError midi_abort(PmInternal *midi)+{+    OSStatus macHostError;+    MIDIEndpointRef endpoint = (MIDIEndpointRef) (intptr_t)+                               pm_descriptors[midi->device_id].descriptor;+    macHostError = MIDIFlushOutput(endpoint);+    return check_hosterror(macHostError,+                           "MIDIFlushOutput() in midi_abort()");+}+++static PmError midi_write_flush(PmInternal *midi, PmTimestamp timestamp)+{+    OSStatus macHostError = noErr;+    coremidi_info_type info = (coremidi_info_type) midi->api_info;+    MIDIEndpointRef endpoint = (MIDIEndpointRef) (intptr_t)+                               pm_descriptors[midi->device_id].descriptor;+    assert(info);+    assert(endpoint);+    if (info->packet != NULL) {+        /* out of space, send the buffer and start refilling it */+        /* update min_next_time each flush to support rate limit */+        UInt64 host_now =  AudioGetCurrentHostTime();+        if (host_now > info->min_next_time) +            info->min_next_time = host_now;+        if (info->is_virtual) {+            macHostError = MIDIReceived(endpoint, info->packetList);+        } else {+            macHostError = MIDISend(portOut, endpoint, info->packetList);+        }+        info->packet = NULL; /* indicate no data in packetList now */+    }+    return check_hosterror(macHostError, (info->is_virtual ?+                                          "MIDIReceived() in midi_write()" :+                                          "MIDISend() in midi_write()"));+}+++static PmError send_packet(PmInternal *midi, Byte *message,+                           unsigned int messageLength, MIDITimeStamp timestamp)+{+    PmError err;+    coremidi_info_type info = (coremidi_info_type) midi->api_info;+    assert(info);+    +    CM_DEBUG printf("add %d to packet %p len %d timestamp %lld @ %lld ns "+                    "(host %lld)\n",+                    message[0], info->packet, messageLength, timestamp,+                    AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()),+                    AudioGetCurrentHostTime());+    info->packet = MIDIPacketListAdd(info->packetList,+                                     sizeof(info->packetBuffer), info->packet,+                                     timestamp, messageLength, message);+    if (info->packet == NULL) {+        /* out of space, send the buffer and start refilling it */+        /* make midi->packet non-null to fool midi_write_flush into sending */+        info->packet = (MIDIPacket *) 4;+        /* timestamp is 0 because midi_write_flush ignores timestamp since+         * timestamps are already in packets. The timestamp parameter is here+         * because other API's need it. midi_write_flush can be called +         * from system-independent code that must be cross-API.+         */+        if ((err = midi_write_flush(midi, 0)) != pmNoError) return err;+        info->packet = MIDIPacketListInit(info->packetList);+        assert(info->packet); /* if this fails, it's a programming error */+        info->packet = MIDIPacketListAdd(info->packetList,+                               sizeof(info->packetBuffer), info->packet,+                               timestamp, messageLength, message);+        assert(info->packet); /* can't run out of space on first message */+    }+    return pmNoError;+}    +++static PmError midi_write_short(PmInternal *midi, PmEvent *event)+{+    PmTimestamp when = event->timestamp;+    PmMessage what = event->message;+    MIDITimeStamp timestamp;+    coremidi_info_type info = (coremidi_info_type) midi->api_info;+    Byte message[4];+    unsigned int messageLength;++    if (info->packet == NULL) {+        info->packet = MIDIPacketListInit(info->packetList);+        /* this can never fail, right? failure would indicate something +           unrecoverable */+        assert(info->packet);+    }+    +    /* PortMidi specifies that incoming timestamps are the receive+     * time.  Devices attach their receive times, but virtual devices+     * do not. Instead, they pass along whatever timestamp was sent to+     * them. We do not know if we are connected to real or virtual+     * device. To avoid wild timestamps on the receiving end, we+     * consider 2 cases: PortMidi timestamp is zero or latency is+     * zero. Both mean send immediately, so we attach the current time+     * which will go out immediately and arrive with a sensible+     * timestamp (not zero and not zero mapped to the client's local+     * time). Otherwise, we assume the timestamp is reasonable. It+     * might be slighly in the past, but we pass it along after+     * translation to MIDITimeStamp units. +     *+     * Compute timestamp: use current time if timestamp is zero or+     * latency is zero. Both mean no timing and send immediately.+     */+    if (when == 0 || midi->latency == 0) {+        timestamp = AudioGetCurrentHostTime();+    } else {  /* translate PortMidi time + latency to CoreMIDI time */+        timestamp = ((UInt64) (when + midi->latency) * (UInt64) 1000000) ++                    info->delta;+        timestamp = AudioConvertNanosToHostTime(timestamp);+    }++    message[0] = Pm_MessageStatus(what);+    message[1] = Pm_MessageData1(what);+    message[2] = Pm_MessageData2(what);+    messageLength = pm_midi_length((int32_t) what);++#ifdef LIMIT_RATE+    /* Make sure we go forward in time. */+    if (timestamp < info->min_next_time) {+        timestamp = info->min_next_time;+    }+    /* Note that if application is way behind and slowly catching up, then+     * timestamps could be increasing faster than real time, and since +     * timestamps are used to estimate data rate, our estimate could be+     * low, causing CoreMIDI to drop packets. This seems very unlikely.+     */+    if (info->isIACdevice || info->is_virtual) {+        info->min_next_time = timestamp + messageLength * +                                          info->host_ticks_per_byte;+    }+#endif+    /* Add this message to the packet list */+    return send_packet(midi, message, messageLength, timestamp);+}+++static PmError midi_begin_sysex(PmInternal *midi, PmTimestamp when)+{+    UInt64 when_ns;+    coremidi_info_type info = (coremidi_info_type) midi->api_info;+    assert(info);+    info->sysex_byte_count = 0;+    +    /* compute timestamp */+    if (when == 0) when = midi->now;+    /* if latency == 0, midi->now is not valid. We will just set it to zero */+    if (midi->latency == 0) when = 0;+    when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) ++              info->delta;+    info->sysex_timestamp =+              (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);+    UInt64 now; /* only make system time call when writing a virtual port */+    if (info->is_virtual && info->sysex_timestamp <+        (now = AudioGetCurrentHostTime())) {+        info->sysex_timestamp = now;+    }++    if (info->packet == NULL) {+        info->packet = MIDIPacketListInit(info->packetList);+        /* this can never fail, right? failure would indicate something +           unrecoverable */+        assert(info->packet);+    }+    return pmNoError;+}+++static PmError midi_end_sysex(PmInternal *midi, PmTimestamp when)+{+    PmError err;+    coremidi_info_type info = (coremidi_info_type) midi->api_info;+    assert(info);+    +#ifdef LIMIT_RATE+    /* make sure we go foreward in time */+    if (info->sysex_timestamp < info->min_next_time)+        info->sysex_timestamp = info->min_next_time;++    if (info->isIACdevice) {+        info->min_next_time = info->sysex_timestamp + info->sysex_byte_count *+                                                      info->host_ticks_per_byte;+    }+#endif+    +    /* now send what's in the buffer */+    err = send_packet(midi, info->sysex_buffer, info->sysex_byte_count,+                      info->sysex_timestamp);+    info->sysex_byte_count = 0;+    if (err != pmNoError) {+        info->packet = NULL; /* flush everything in the packet list */+    }+    return err;+}+++static PmError midi_write_byte(PmInternal *midi, unsigned char byte, +                               PmTimestamp timestamp)+{+    coremidi_info_type info = (coremidi_info_type) midi->api_info;+    assert(info);+    if (info->sysex_byte_count >= SYSEX_BUFFER_SIZE) {+        PmError err = midi_end_sysex(midi, timestamp);+        if (err != pmNoError) return err;+    }+    info->sysex_buffer[info->sysex_byte_count++] = byte;+    return pmNoError;+}+++static PmError midi_write_realtime(PmInternal *midi, PmEvent *event)+{+    /* to send a realtime message during a sysex message, first+       flush all pending sysex bytes into packet list */+    PmError err = midi_end_sysex(midi, 0);+    if (err != pmNoError) return err;+    /* then we can just do a normal midi_write_short */+    return midi_write_short(midi, event);+}+++static unsigned int midi_check_host_error(PmInternal *midi)+{+    return FALSE;+}+++MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp)+{+    UInt64 nanos;+    if (timestamp <= 0) {+        return (MIDITimeStamp)0;+    } else {+        nanos = (UInt64)timestamp * (UInt64)1000000;+        return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos);+    }+}+++PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp)+{+    UInt64 nanos;+    nanos = AudioConvertHostTimeToNanos(timestamp);+    return (PmTimestamp)(nanos / (UInt64)1000000);+}+++/* Code taken from http://developer.apple.com/qa/qa2004/qa1374.html+ *+ * Obtain the name of an endpoint without regard for whether it has connections.+ * The result should be released by the caller.+ */+CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal,+                         int *iac_flag)+{+    CFMutableStringRef result = CFStringCreateMutable(NULL, 0);+    CFStringRef str;+    *iac_flag = FALSE;+  +    /* begin with the endpoint's name */+    str = NULL;+    MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str);+    if (str != NULL) {+        CFStringAppend(result, str);+        CFRelease(str);+    }+    MIDIEntityRef entity = NULL_REF;+    MIDIEndpointGetEntity(endpoint, &entity);+    if (entity == NULL_REF) {+        /* probably virtual */+        return result;+    }+    if (!isExternal) { /* detect IAC devices */+        /* extern const CFStringRef kMIDIPropertyDriverOwner; */+        MIDIObjectGetStringProperty(entity, kMIDIPropertyDriverOwner, &str);+        if (str != NULL) {+            char s[32]; /* driver name may truncate, but that's OK */+            CFStringGetCString(str, s, 31, kCFStringEncodingUTF8);+            s[31] = 0;  /* make sure it is terminated just to be safe */+            CM_DEBUG printf("driver %s\n", s);+            *iac_flag = (strcmp(s, "com.apple.AppleMIDIIACDriver") == 0);+        }+    }++    if (CFStringGetLength(result) == 0) {+        /* endpoint name has zero length -- try the entity */+        str = NULL;+        MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str);+        if (str != NULL) {+            CFStringAppend(result, str);+            CFRelease(str);+        }+    }+    /* now consider the device's name */+    MIDIDeviceRef device = NULL_REF;+    MIDIEntityGetDevice(entity, &device);+    if (device == NULL_REF)+        return result;+  +    str = NULL;+    MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str);+    if (CFStringGetLength(result) == 0) {+        CFRelease(result);+        return str;+    }+    if (str != NULL) {+        /* if an external device has only one entity, throw away+           the endpoint name and just use the device name+        */+        if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) {+            CFRelease(result);+            return str;+        } else {+            if (CFStringGetLength(str) == 0) {+                CFRelease(str);+                return result;+            }+            /* does the entity name already start with the device name?+               (some drivers do this though they shouldn't)+               if so, do not prepend+            */+            if (CFStringCompareWithOptions(result, /* endpoint name */+                        str, /* device name */+                        CFRangeMake(0, CFStringGetLength(str)), 0) != +                kCFCompareEqualTo) {+                /* prepend the device name to the entity name */+                if (CFStringGetLength(result) > 0)+                    CFStringInsert(result, 0, CFSTR(" "));+                CFStringInsert(result, 0, str);+            }+            CFRelease(str);+        }+    }+    return result;+}+++/* Obtain the name of an endpoint, following connections.+   The result should be released by the caller.+*/+static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint,+                                         int *iac_flag)+{+    CFMutableStringRef result = CFStringCreateMutable(NULL, 0);+    CFStringRef str;+    OSStatus err;+    long i;+  +    /* Does the endpoint have connections? */+    CFDataRef connections = NULL;+    long nConnected = 0;+    bool anyStrings = false;+    err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID,+                                    &connections);+    if (connections != NULL) {+        /* It has connections, follow them+           Concatenate the names of all connected devices+        */+        nConnected = CFDataGetLength(connections) / +                     (int32_t) sizeof(MIDIUniqueID);+        if (nConnected) {+            const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections));+            for (i = 0; i < nConnected; ++i, ++pid) {+                MIDIUniqueID id = EndianS32_BtoN(*pid);+                MIDIObjectRef connObject;+                MIDIObjectType connObjectType;+                err = MIDIObjectFindByUniqueID(id, &connObject, +                                               &connObjectType);+                if (err == noErr) {+                    if (connObjectType == kMIDIObjectType_ExternalSource  ||+                        connObjectType == kMIDIObjectType_ExternalDestination) {+                        /* Connected to an external device's endpoint (>=10.3) */+                        str = EndpointName((MIDIEndpointRef)(connObject), true,+                                           iac_flag);+                    } else {+                        /* Connected to an external device (10.2) +                           (or something else, catch-all)+                        */+                        str = NULL;+                        MIDIObjectGetStringProperty(connObject, +                                                    kMIDIPropertyName, &str);+                    }+                    if (str != NULL) {+                        if (anyStrings)+                            CFStringAppend(result, CFSTR(", "));+                        else anyStrings = true;+                        CFStringAppend(result, str);+                        CFRelease(str);+                    }+                }+            }+        }+        CFRelease(connections);+    }+    if (anyStrings)+        return result; /* caller should release result */++    CFRelease(result);++    /* Here, either the endpoint had no connections, or we failed to+       obtain names for any of them.+    */+    return EndpointName(endpoint, false, iac_flag);+}+++/* cm_get_full_endpoint_name -- returns UTF-8 namem for endpoint.+ *     caller is owner and responsible for pm_free'ing the string+ */+char *cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag)+{+    /* Thanks to Dan Wilcox for fixes for Unicode handling+     * and kichikuou at github for fixing buffer size calculation.+     */+    CFStringRef fullName = ConnectedEndpointName(endpoint, iac_flag);+    CFIndex len = CFStringGetLength(fullName) + 1;+    /* (len seems to be length in Unicode characters, although docs add+     *  the confusing explanation "in terms of UTF-16 code pairs")+     */+    CFIndex max_byte_len = CFStringGetMaximumSizeForEncoding(+                                   len, kCFStringEncodingUTF8) + 1;+    char* pmname = (char *) pm_alloc(max_byte_len);++    /* copy the string into our buffer; note that there may be some wasted+       space, but the total waste is not large */+    CFStringGetCString(fullName, pmname, max_byte_len, kCFStringEncodingUTF8);++    /* clean up */+    if (fullName) CFRelease(fullName);+    return pmname;+}+++pm_fns_node pm_macosx_in_dictionary = {+    none_write_short,+    none_sysex,+    none_sysex,+    none_write_byte,+    none_write_short,+    none_write_flush,+    none_synchronize,+    midi_in_open,+    midi_abort,+    midi_in_close,+    success_poll,+    midi_check_host_error+};++pm_fns_node pm_macosx_out_dictionary = {+    midi_write_short,+    midi_begin_sysex,+    midi_end_sysex,+    midi_write_byte,+    midi_write_realtime,+    midi_write_flush,+    midi_synchronize,+    midi_out_open,+    midi_abort,+    midi_out_close,+    success_poll,+    midi_check_host_error+};+++/* We do nothing with callbacks, but generating the callbacks also+ * updates CoreMIDI state. Callback may not be essential, but calling+ * the CFRunLoopRunInMode is necessary.+ */+void cm_notify(const MIDINotification *msg, void *refCon)+{+    /*  for debugging, trace change notifications: +    const char *descr[] = {+        "undefined (0)",+      	"kMIDIMsgSetupChanged",+        "kMIDIMsgObjectAdded",+        "kMIDIMsgObjectRemoved",+        "kMIDIMsgPropertyChanged",+        "kMIDIMsgThruConnectionsChanged",+        "kMIDIMsgSerialPortOwnerChanged",+        "kMIDIMsgIOError"};++    printf("MIDI Notify, messageID %d (%s)\n", (int) msg->messageID,+           descr[(int) msg->messageID]);+    */+    return;+}+++PmError pm_macosxcm_init(void)+{+    ItemCount numInputs, numOutputs, numDevices;+    MIDIEndpointRef endpoint;+    OSStatus macHostError = noErr;+    const char *error_text;++    memset(isIAC, 0, sizeof(isIAC)); /* initialize all FALSE */++    /* Register interface CoreMIDI with create_virtual fn */+    pm_add_interf("CoreMIDI", &midi_create_virtual, &midi_delete_virtual);+    /* no check for error return because this always succeeds */++    /* Determine the number of MIDI devices on the system */+    numDevices = MIDIGetNumberOfDevices();++    /* Return prematurely if no devices exist on the system+       Note that this is not an error. There may be no devices.+       Pm_CountDevices() will return zero, which is correct and+       useful information+     */+    if (numDevices <= 0) {+        return pmNoError;+    }++    /* Initialize the client handle */+    if (client == NULL_REF) {+        macHostError = MIDIClientCreate(CFSTR("PortMidi"), &cm_notify, NULL,+                                        &client);+    } else {  /* see notes above on device scanning */+        for (int i = 0; i < 100; i++) {+            /* look for any changes before scanning for devices: */+            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);+            if (i % 5 == 0) Pt_Sleep(1);  /* insert 20 delays */+        }+    }+    if (macHostError != noErr) {+        error_text = "MIDIClientCreate() in pm_macosxcm_init()";+        goto error_return;+    }+    numInputs = MIDIGetNumberOfSources();+    numOutputs = MIDIGetNumberOfDestinations();++    /* Create the input port */+    macHostError = MIDIInputPortCreate(client, CFSTR("Input port"), +                                       device_read_callback, NULL, &portIn);+    if (macHostError != noErr) {+        error_text = "MIDIInputPortCreate() in pm_macosxcm_init()";+        goto error_return;+    }++    /* Create the output port */+    macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut);+    if (macHostError != noErr) {+        error_text = "MIDIOutputPortCreate() in pm_macosxcm_init()";+        goto error_return;+    }++    /* Iterate over the MIDI input devices */+    for (int i = 0; i < numInputs; i++) {+        int iac_flag;+        endpoint = MIDIGetSource(i);+        if (endpoint == NULL_REF) {+            continue;+        }+        /* Register this device with PortMidi */+        char *name = cm_get_full_endpoint_name(endpoint, &iac_flag);+        pm_add_device("CoreMIDI", name, TRUE, FALSE,+                      (void *) (intptr_t) endpoint, &pm_macosx_in_dictionary);+        /* pm_add_device copies name, so it is no longer needed */+        pm_free(name);+    }++    /* Iterate over the MIDI output devices */+    for (int i = 0; i < numOutputs; i++) {+        int iac_flag;+        PmDeviceID id;+        endpoint = MIDIGetDestination(i);+        if (endpoint == NULL_REF) {+            continue;+        }+        /* Register this device with PortMidi */+        char *name = cm_get_full_endpoint_name(endpoint, &iac_flag);+        id = pm_add_device("CoreMIDI", name, FALSE, FALSE,+                (void *) (intptr_t) endpoint, &pm_macosx_out_dictionary);+        /* pm_add_device copies name, so it is no longer needed */+        pm_free(name);++        /* if this is an IAC device, tuck that info away for write functions */+        if (iac_flag && id <= MAX_IAC_NUM) {+            isIAC[id] = TRUE;+        }+    }+    return pmNoError;+    +error_return:+    pm_macosxcm_term(); /* clear out any opened ports */+    return check_hosterror(macHostError, error_text);+}++void pm_macosxcm_term(void)+{+    /* docs say do not explicitly dispose of client+       if (client != NULL_REF) MIDIClientDispose(client); */+    if (portIn != NULL_REF) MIDIPortDispose(portIn);+    if (portOut != NULL_REF) MIDIPortDispose(portOut); }
portmidi/pm_mac/pmmacosxcm.h view
@@ -1,4 +1,4 @@-/* system-specific definitions */
-
-PmError pm_macosxcm_init(void);
+/* system-specific definitions */++PmError pm_macosxcm_init(void); void pm_macosxcm_term(void);
portmidi/pm_win/pmwin.c view
@@ -1,114 +1,98 @@-/* pmwin.c -- PortMidi os-dependent code */
-
-/* This file only needs to implement:
-       pm_init(), which calls various routines to register the 
-           available midi devices,
-       Pm_GetDefaultInputDeviceID(), and
-       Pm_GetDefaultOutputDeviceID().
-   This file must
-   be separate from the main portmidi.c file because it is system
-   dependent, and it is separate from, say, pmwinmm.c, because it
-   might need to register devices for winmm, directx, and others.
- */
-
-#include "stdlib.h"
-#include "portmidi.h"
-#include "pmutil.h"
-#include "pminternal.h"
-#include "pmwinmm.h"
-#ifdef USE_DLL_FOR_CLEANUP
-#include "pmdll.h" /* used to close ports on exit */
-#endif
-#ifdef DEBUG
-#include "stdio.h"
-#endif
-
-/* pm_exit is called when the program exits.
-   It calls pm_term to make sure PortMidi is properly closed.
-   If DEBUG is on, we prompt for input to avoid losing error messages.
- */
-static void pm_exit(void) {
-    pm_term();
-#ifdef DEBUG
-#define STRING_MAX 80
-    {
-        char line[STRING_MAX];
-        printf("Type ENTER...\n");
-        /* note, w/o this prompting, client console application can not see one
-           of its errors before closing. */
-        fgets(line, STRING_MAX, stdin);
-    }
-#endif
-}
-
-
-/* pm_init is the windows-dependent initialization.*/
-void pm_init(void)
-{
-#ifdef USE_DLL_FOR_CLEANUP
-    /* we were hoping a DLL could offer more robust cleanup after errors,
-       but the DLL does not seem to run after crashes. Thus, the atexit()
-       mechanism is just as powerful, and simpler to implement.
-     */
-    pm_set_close_function(pm_exit);
-#ifdef DEBUG
-    printf("registered pm_term with cleanup DLL\n");
-#endif
-#else
-    atexit(pm_exit);
-#ifdef DEBUG
-    printf("registered pm_exit with atexit()\n");
-#endif
-#endif
-    pm_winmm_init();
-    /* initialize other APIs (DirectX?) here */
-}
-
-
-void pm_term(void) {
-    pm_winmm_term();
-}
-
-
-PmDeviceID Pm_GetDefaultInputDeviceID() {
-    /* This routine should check the environment and the registry
-       as specified in portmidi.h, but for now, it just returns
-       the first device of the proper input/output flavor.
-     */
-    int i;
-    Pm_Initialize(); /* make sure descriptors exist! */
-    for (i = 0; i < pm_descriptor_index; i++) {
-        if (descriptors[i].pub.input) {
-            return i;
-        }
-    }
-    return pmNoDevice;
-}
-
-PmDeviceID Pm_GetDefaultOutputDeviceID() {
-    /* This routine should check the environment and the registry
-       as specified in portmidi.h, but for now, it just returns
-       the first device of the proper input/output flavor.
-     */
-    int i;
-    Pm_Initialize(); /* make sure descriptors exist! */
-    for (i = 0; i < pm_descriptor_index; i++) {
-        if (descriptors[i].pub.output) {
-            return i;
-        }
-    }
-    return pmNoDevice;
-    return 0;
-}
-
-#include "stdio.h" 
-
-void *pm_alloc(size_t s) {
-    return malloc(s); 
-}
-
-
-void pm_free(void *ptr) { 
-    free(ptr); 
-}
-
+/* pmwin.c -- PortMidi os-dependent code */++/* This file only needs to implement:+       pm_init(), which calls various routines to register the +           available midi devices,+       Pm_GetDefaultInputDeviceID(), and+       Pm_GetDefaultOutputDeviceID().+   This file must+   be separate from the main portmidi.c file because it is system+   dependent, and it is separate from, say, pmwinmm.c, because it+   might need to register devices for winmm, directx, and others.++ */++#include "stdlib.h"+#include "portmidi.h"+#include "pmutil.h"+#include "pminternal.h"+#include "pmwinmm.h"+#ifdef DEBUG+#include "stdio.h"+#endif+#include <windows.h>++/* pm_exit is called when the program exits.+   It calls pm_term to make sure PortMidi is properly closed.+   If DEBUG is on, we prompt for input to avoid losing error messages.+ */+static void pm_exit(void) {+    pm_term();+}+++static BOOL WINAPI ctrl_c_handler(DWORD fdwCtrlType)+{+    exit(1);  /* invokes pm_exit() */+    ExitProcess(1);  /* probably never called */+    return TRUE;+}++/* pm_init is the windows-dependent initialization.*/+void pm_init(void)+{+    atexit(pm_exit);+    SetConsoleCtrlHandler(ctrl_c_handler, TRUE);+#ifdef DEBUG+    printf("registered pm_exit with atexit()\n");+#endif+    pm_winmm_init();+    /* initialize other APIs (DirectX?) here */+}+++void pm_term(void) {+    pm_winmm_term();+}+++static PmDeviceID pm_get_default_device_id(int is_input, char *key) {+#define PATTERN_MAX 256+    /* Find first input or device -- this is the default. */+    PmDeviceID id = pmNoDevice;+    int i;+    Pm_Initialize(); /* make sure descriptors exist! */+    for (i = 0; i < pm_descriptor_len; i++) {+        if (pm_descriptors[i].pub.input == is_input) {+            id = i;+            break;+        }+    }+    return id;+}+++PmDeviceID Pm_GetDefaultInputDeviceID(void) {+    return pm_get_default_device_id(TRUE, +           "/P/M_/R/E/C/O/M/M/E/N/D/E/D_/I/N/P/U/T_/D/E/V/I/C/E");+}+++PmDeviceID Pm_GetDefaultOutputDeviceID(void) {+  return pm_get_default_device_id(FALSE,+          "/P/M_/R/E/C/O/M/M/E/N/D/E/D_/O/U/T/P/U/T_/D/E/V/I/C/E");+}+++#include "stdio.h" ++void *pm_alloc(size_t s) {+    return malloc(s); +}+++void pm_free(void *ptr) { +    free(ptr); +}++
portmidi/pm_win/pmwinmm.c view
@@ -1,1449 +1,1196 @@-/* pmwinmm.c -- system specific definitions */
-
-#ifndef _WIN32_WINNT
-    /* without this define, InitializeCriticalSectionAndSpinCount is 
-     * undefined. This version level means "Windows 2000 and higher" 
-     */
-    #define _WIN32_WINNT 0x0500
-#endif
-
-#include "windows.h"
-#include "mmsystem.h"
-#include "portmidi.h"
-#include "pmutil.h"
-#include "pminternal.h"
-#include "pmwinmm.h"
-#include <string.h>
-#include "porttime.h"
-
-/* asserts used to verify portMidi code logic is sound; later may want
-    something more graceful */
-#include <assert.h>
-#define DEBUG 1
-#ifdef DEBUG
-/* this printf stuff really important for debugging client app w/host errors.
-    probably want to do something else besides read/write from/to console
-    for portability, however */
-#define STRING_MAX 80
-#include "stdio.h"
-#endif
-
-#define streql(x, y) (strcmp(x, y) == 0)
-
-#define MIDI_SYSEX      0xf0
-#define MIDI_EOX        0xf7
-
-/* callback routines */
-static void CALLBACK winmm_in_callback(HMIDIIN hMidiIn,
-                                       WORD wMsg, DWORD dwInstance, 
-                                       DWORD dwParam1, DWORD dwParam2);
-static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,
-                                              DWORD dwInstance, DWORD dwParam1, 
-                                              DWORD dwParam2);
-static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg,
-                                        DWORD dwInstance, DWORD dwParam1, 
-                                        DWORD dwParam2);
-
-extern pm_fns_node pm_winmm_in_dictionary;
-extern pm_fns_node pm_winmm_out_dictionary;
-
-static void winmm_out_delete(PmInternal *midi); /* forward reference */
-
-/*
-A note about buffers: WinMM seems to hold onto buffers longer than
-one would expect, e.g. when I tried using 2 small buffers to send
-long sysex messages, at some point WinMM held both buffers. This problem
-was fixed by making buffers bigger. Therefore, it seems that there should 
-be enough buffer space to hold a whole sysex message. 
-
-The bufferSize passed into Pm_OpenInput (passed into here as buffer_len)
-will be used to estimate the largest sysex message (= buffer_len * 4 bytes).
-Call that the max_sysex_len = buffer_len * 4.
-
-For simple midi output (latency == 0), allocate 3 buffers, each with half
-the size of max_sysex_len, but each at least 256 bytes.
-
-For stream output, there will already be enough space in very short
-buffers, so use them, but make sure there are at least 16.
-
-For input, use many small buffers rather than 2 large ones so that when 
-there are short sysex messages arriving frequently (as in control surfaces)
-there will be more free buffers to fill. Use max_sysex_len / 64 buffers,
-but at least 16, of size 64 bytes each.
-
-The following constants help to represent these design parameters:
-*/
-#define NUM_SIMPLE_SYSEX_BUFFERS 3
-#define MIN_SIMPLE_SYSEX_LEN 256
-
-#define MIN_STREAM_BUFFERS 16
-#define STREAM_BUFFER_LEN 24
-
-#define INPUT_SYSEX_LEN 64
-#define MIN_INPUT_BUFFERS 16
-
-/* if we run out of space for output (assume this is due to a sysex msg,
-   expand by up to NUM_EXPANSION_BUFFERS in increments of EXPANSION_BUFFER_LEN
- */
-#define NUM_EXPANSION_BUFFERS 128
-#define EXPANSION_BUFFER_LEN 1024
-
-/* A sysex buffer has 3 DWORDS as a header plus the actual message size */
-#define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(long)*3)
-/* A MIDIHDR with a sysex message is the buffer length plus the header size */
-#define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_BUFFER_LENGTH(x) + sizeof(MIDIHDR))
-#ifdef USE_SYSEX_BUFFERS
-/* Size of a MIDIHDR with a buffer contaning multiple MIDIEVENT structures */
-#define MIDIHDR_SIZE(x) ((x) + sizeof(MIDIHDR))
-#endif
-
-/*
-==============================================================================
-win32 mmedia system specific structure passed to midi callbacks
-==============================================================================
-*/
-
-/* global winmm device info */
-MIDIINCAPS *midi_in_caps = NULL;
-MIDIINCAPS midi_in_mapper_caps;
-UINT midi_num_inputs = 0;
-MIDIOUTCAPS *midi_out_caps = NULL;
-MIDIOUTCAPS midi_out_mapper_caps;
-UINT midi_num_outputs = 0;
-
-/* per device info */
-typedef struct midiwinmm_struct {
-    union {
-        HMIDISTRM stream;   /* windows handle for stream */
-        HMIDIOUT out;       /* windows handle for out calls */
-        HMIDIIN in;         /* windows handle for in calls */
-    } handle;
-
-    /* midi output messages are sent in these buffers, which are allocated
-     * in a round-robin fashion, using next_buffer as an index
-     */
-    LPMIDIHDR *buffers;     /* pool of buffers for midi in or out data */
-    int max_buffers;        /* length of buffers array */
-    int buffers_expanded;   /* buffers array expanded for extra msgs? */
-    int num_buffers;        /* how many buffers allocated in buffers array */
-    int next_buffer;        /* index of next buffer to send */
-    HANDLE buffer_signal;   /* used to wait for buffer to become free */
-#ifdef USE_SYSEX_BUFFERS
-    /* sysex buffers will be allocated only when
-     * a sysex message is sent. The size of the buffer is fixed.
-     */
-    LPMIDIHDR sysex_buffers[NUM_SYSEX_BUFFERS]; /* pool of buffers for sysex data */
-    int next_sysex_buffer;      /* index of next sysexbuffer to send */
-#endif
-    unsigned long last_time;    /* last output time */
-    int first_message;          /* flag: treat first message differently */
-    int sysex_mode;             /* middle of sending sysex */
-    unsigned long sysex_word;   /* accumulate data when receiving sysex */
-    unsigned int sysex_byte_count; /* count how many received */
-    LPMIDIHDR hdr;              /* the message accumulating sysex to send */
-    unsigned long sync_time;    /* when did we last determine delta? */
-    long delta;                 /* difference between stream time and
-                                       real time */
-    int error;                  /* host error from doing port midi call */
-    CRITICAL_SECTION lock;      /* prevents reentrant callbacks (input only) */
-} midiwinmm_node, *midiwinmm_type;
-
-
-/*
-=============================================================================
-general MIDI device queries
-=============================================================================
-*/
-static void pm_winmm_general_inputs()
-{
-    UINT i;
-    WORD wRtn;
-    midi_num_inputs = midiInGetNumDevs();
-    midi_in_caps = (MIDIINCAPS *) pm_alloc(sizeof(MIDIINCAPS) * 
-                                           midi_num_inputs);
-    if (midi_in_caps == NULL) {
-        /* if you can't open a particular system-level midi interface
-         * (such as winmm), we just consider that system or API to be
-         * unavailable and move on without reporting an error.
-         */
-        return;
-    }
-
-    for (i = 0; i < midi_num_inputs; i++) {
-        wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) & midi_in_caps[i],
-                                sizeof(MIDIINCAPS));
-        if (wRtn == MMSYSERR_NOERROR) {
-            /* ignore errors here -- if pm_descriptor_max is exceeded, some
-               devices will not be accessible. */
-            pm_add_device("MMSystem", midi_in_caps[i].szPname, TRUE,
-                          (void *) i, &pm_winmm_in_dictionary);
-        }
-    }
-}
-
-
-static void pm_winmm_mapper_input()
-{
-    WORD wRtn;
-    /* Note: if MIDIMAPPER opened as input (documentation implies you
-        can, but current system fails to retrieve input mapper
-        capabilities) then you still should retrieve some formof
-        setup info. */
-    wRtn = midiInGetDevCaps((UINT) MIDIMAPPER,
-                            (LPMIDIINCAPS) & midi_in_mapper_caps, 
-                            sizeof(MIDIINCAPS));
-    if (wRtn == MMSYSERR_NOERROR) {
-        pm_add_device("MMSystem", midi_in_mapper_caps.szPname, TRUE,
-                      (void *) MIDIMAPPER, &pm_winmm_in_dictionary);
-    }
-}
-
-
-static void pm_winmm_general_outputs()
-{
-    UINT i;
-    DWORD wRtn;
-    midi_num_outputs = midiOutGetNumDevs();
-    midi_out_caps = pm_alloc( sizeof(MIDIOUTCAPS) * midi_num_outputs );
-
-    if (midi_out_caps == NULL) {
-        /* no error is reported -- see pm_winmm_general_inputs */
-        return ;
-    }
-
-    for (i = 0; i < midi_num_outputs; i++) {
-        wRtn = midiOutGetDevCaps(i, (LPMIDIOUTCAPS) & midi_out_caps[i],
-                                 sizeof(MIDIOUTCAPS));
-        if (wRtn == MMSYSERR_NOERROR) {
-            pm_add_device("MMSystem", midi_out_caps[i].szPname, FALSE,
-                          (void *) i, &pm_winmm_out_dictionary);
-        }
-    }
-}
-
-
-static void pm_winmm_mapper_output()
-{
-    WORD wRtn;
-    /* Note: if MIDIMAPPER opened as output (pseudo MIDI device
-        maps device independent messages into device dependant ones,
-        via NT midimapper program) you still should get some setup info */
-    wRtn = midiOutGetDevCaps((UINT) MIDIMAPPER, (LPMIDIOUTCAPS)
-                             & midi_out_mapper_caps, sizeof(MIDIOUTCAPS));
-    if (wRtn == MMSYSERR_NOERROR) {
-        pm_add_device("MMSystem", midi_out_mapper_caps.szPname, FALSE,
-                      (void *) MIDIMAPPER, &pm_winmm_out_dictionary);
-    }
-}
-
-
-/*
-=========================================================================================
-host error handling
-=========================================================================================
-*/
-static unsigned int winmm_has_host_error(PmInternal * midi)
-{
-    midiwinmm_type m = (midiwinmm_type)midi->descriptor;
-    return m->error;
-}
-
-
-/* str_copy_len -- like strcat, but won't overrun the destination string */
-/*
- * returns length of resulting string
- */
-static int str_copy_len(char *dst, char *src, int len)
-{
-    strncpy(dst, src, len);
-    /* just in case suffex is greater then len, terminate with zero */
-    dst[len - 1] = 0;
-    return strlen(dst);
-}
-
-
-static void winmm_get_host_error(PmInternal * midi, char * msg, UINT len)
-{
-    /* precondition: midi != NULL */
-    midiwinmm_node * m = (midiwinmm_node *) midi->descriptor;
-    char *hdr1 = "Host error: ";
-    char *hdr2 = "Host callback error: ";
-
-    msg[0] = 0; /* initialize result string to empty */
-
-    if (descriptors[midi->device_id].pub.input) {
-        /* input and output use different winmm API calls */
-        if (m) { /* make sure there is an open device to examine */
-            if (m->error != MMSYSERR_NOERROR) {
-                int n = str_copy_len(msg, hdr1, len);
-                /* read and record host error */
-                int err = midiInGetErrorText(m->error, msg + n, len - n);
-                assert(err == MMSYSERR_NOERROR);
-                m->error = MMSYSERR_NOERROR;
-            }
-        }
-    } else { /* output port */
-        if (m) {
-            if (m->error != MMSYSERR_NOERROR) {
-                int n = str_copy_len(msg, hdr1, len);
-                int err = midiOutGetErrorText(m->error, msg + n, len - n);
-                assert(err == MMSYSERR_NOERROR);
-                m->error = MMSYSERR_NOERROR;
-            }
-        }
-    }
-}
-
-
-/*
-=============================================================================
-buffer handling
-=============================================================================
-*/
-static MIDIHDR *allocate_buffer(long data_size)
-{
-    LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size));
-    MIDIEVENT *evt;
-    if (!hdr) return NULL;
-    evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */
-    hdr->lpData = (LPSTR) evt;
-    hdr->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size);
-    hdr->dwBytesRecorded = 0;
-    hdr->dwFlags = 0;
-    hdr->dwUser = hdr->dwBufferLength;
-    return hdr;
-}
-
-#ifdef USE_SYSEX_BUFFERS
-static MIDIHDR *allocate_sysex_buffer(long data_size)
-{
-    /* we're actually allocating more than data_size because the buffer 
-     * will include the MIDIEVENT header in addition to the data 
-     */
-    LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size));
-    MIDIEVENT *evt;
-    if (!hdr) return NULL;
-    evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */
-    hdr->lpData = (LPSTR) evt;
-    hdr->dwFlags = 0;
-    hdr->dwUser = 0;
-    return hdr;
-}
-#endif
-
-static PmError allocate_buffers(midiwinmm_type m, long data_size, long count)
-{
-    int i;
-    /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */
-    m->num_buffers = 0; /* in case no memory can be allocated */
-    m->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count);
-    if (!m->buffers) return pmInsufficientMemory;
-    m->max_buffers = count;
-    for (i = 0; i < count; i++) {
-        LPMIDIHDR hdr = allocate_buffer(data_size);
-        if (!hdr) { /* free everything allocated so far and return */
-            for (i = i - 1; i >= 0; i--) pm_free(m->buffers[i]);
-            pm_free(m->buffers);
-            m->max_buffers = 0;
-            return pmInsufficientMemory;
-        }
-        m->buffers[i] = hdr; /* this may be NULL if allocation fails */
-    }
-    m->num_buffers = count;
-    return pmNoError;
-}
-
-#ifdef USE_SYSEX_BUFFERS
-static PmError allocate_sysex_buffers(midiwinmm_type m, long data_size)
-{
-    PmError rslt = pmNoError;
-    /* sysex_buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */
-    int i;
-    for (i = 0; i < NUM_SYSEX_BUFFERS; i++) {
-        LPMIDIHDR hdr = allocate_sysex_buffer(data_size);
-
-        if (!hdr) rslt = pmInsufficientMemory;
-        m->sysex_buffers[i] = hdr; /* this may be NULL if allocation fails */
-        hdr->dwFlags = 0; /* mark as free */
-    }
-    return rslt;
-}
-#endif
-
-#ifdef USE_SYSEX_BUFFERS
-static LPMIDIHDR get_free_sysex_buffer(PmInternal *midi)
-{
-    LPMIDIHDR r = NULL;
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    if (!m->sysex_buffers[0]) {
-        if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER)) {
-            return NULL;
-        }
-    }
-    /* busy wait until we find a free buffer */
-    while (TRUE) {
-        int i;
-        for (i = 0; i < NUM_SYSEX_BUFFERS; i++) {
-            /* cycle through buffers, modulo NUM_SYSEX_BUFFERS */
-            m->next_sysex_buffer++;
-            if (m->next_sysex_buffer >= NUM_SYSEX_BUFFERS) m->next_sysex_buffer = 0;
-            r = m->sysex_buffers[m->next_sysex_buffer];
-            if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_sysex_buffer;
-        }
-        /* after scanning every buffer and not finding anything, block */
-        if (WaitForSingleObject(m->buffer_signal, 1000) == WAIT_TIMEOUT) {
-#ifdef DEBUG
-            printf("PortMidi warning: get_free_sysex_buffer() wait timed out after 1000ms\n");
-#endif
-        }
-    }
-found_sysex_buffer:
-    r->dwBytesRecorded = 0;
-    r->dwBufferLength = 0; /* changed to correct value later */
-    return r;
-}
-#endif
-
-static LPMIDIHDR get_free_output_buffer(PmInternal *midi)
-{
-    LPMIDIHDR r = NULL;
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    while (TRUE) {
-        int i;
-        for (i = 0; i < m->num_buffers; i++) {
-            /* cycle through buffers, modulo m->num_buffers */
-            m->next_buffer++;
-            if (m->next_buffer >= m->num_buffers) m->next_buffer = 0;
-            r = m->buffers[m->next_buffer];
-            if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer;
-        }
-        /* after scanning every buffer and not finding anything, block */
-        if (WaitForSingleObject(m->buffer_signal, 1000) == WAIT_TIMEOUT) {
-#ifdef DEBUG
-            printf("PortMidi warning: get_free_output_buffer() wait timed out after 1000ms\n");
-#endif
-            /* if we're trying to send a sysex message, maybe the 
-             * message is too big and we need more message buffers.
-             * Expand the buffer pool by 128KB using 1024-byte buffers.
-             */
-            /* first, expand the buffers array if necessary */
-            if (!m->buffers_expanded) {
-                LPMIDIHDR *new_buffers = (LPMIDIHDR *) pm_alloc(
-                        (m->num_buffers + NUM_EXPANSION_BUFFERS) * 
-                        sizeof(LPMIDIHDR));
-                /* if no memory, we could return a no-memory error, but user
-                 * probably will be unprepared to deal with it. Maybe the
-                 * MIDI driver is temporarily hung so we should just wait.
-                 * I don't know the right answer, but waiting is easier.
-                 */
-                if (!new_buffers) continue;
-                /* copy buffers to new_buffers and replace buffers */
-                memcpy(new_buffers, m->buffers, 
-                       m->num_buffers * sizeof(LPMIDIHDR));
-                pm_free(m->buffers);
-                m->buffers = new_buffers;
-                m->max_buffers = m->num_buffers + NUM_EXPANSION_BUFFERS;
-                m->buffers_expanded = TRUE;
-            }
-            /* next, add one buffer and return it */
-            if (m->num_buffers < m->max_buffers) {
-                r = allocate_buffer(EXPANSION_BUFFER_LEN);
-                /* again, if there's no memory, we may not really be 
-                 * dead -- maybe the system is temporarily hung and
-                 * we can just wait longer for a message buffer */
-                if (!r) continue;
-                m->buffers[m->num_buffers++] = r;
-                goto found_buffer; /* break out of 2 loops */
-            }
-            /* else, we've allocated all NUM_EXPANSION_BUFFERS buffers,
-             * and we have no free buffers to send. We'll just keep
-             * polling to see if any buffers show up.
-             */
-        }
-    }
-found_buffer:
-    r->dwBytesRecorded = 0;
-    /* actual buffer length is saved in dwUser field */
-    r->dwBufferLength = (DWORD) r->dwUser;
-    return r;
-}
-
-#ifdef EXPANDING_SYSEX_BUFFERS
-note: this is not working code, but might be useful if you want
-      to grow sysex buffers.
-static PmError resize_sysex_buffer(PmInternal *midi, long old_size, long new_size)
-{
-    LPMIDIHDR big;
-    int i;
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    /* buffer must be smaller than 64k, but be also a multiple of 4 */
-    if (new_size > 65520) {
-        if (old_size >= 65520)
-            return pmBufferMaxSize;
-        else
-            new_size = 65520;
-    }
-    /* allocate a bigger message  */
-    big = allocate_sysex_buffer(new_size);
-    /* printf("expand to %d bytes\n", new_size);*/
-    if (!big) return pmInsufficientMemory;
-    m->error = midiOutPrepareHeader(m->handle.out, big, sizeof(MIDIHDR));
-    if (m->error) {
-        pm_free(big);
-        return pmHostError;
-    }
-    /* make sure we're not going to overwrite any memory */
-    assert(old_size <= new_size);
-    memcpy(big->lpData, m->hdr->lpData, old_size);
-    /* keep track of how many sysex bytes are in message so far */
-    big->dwBytesRecorded = m->hdr->dwBytesRecorded;
-    big->dwBufferLength = new_size;
-    /* find which buffer this was, and replace it */
-    for (i = 0; i < NUM_SYSEX_BUFFERS; i++) {
-        if (m->sysex_buffers[i] == m->hdr) {
-            m->sysex_buffers[i] = big;
-            m->sysex_buffer_size[i] = new_size;
-            pm_free(m->hdr);
-            m->hdr = big;
-            break;
-        }
-    }
-    assert(i != NUM_SYSEX_BUFFERS);
-
-    return pmNoError;
-}
-#endif
-
-/*
-=========================================================================================
-begin midi input implementation
-=========================================================================================
-*/
-
-
-static PmError allocate_input_buffer(HMIDIIN h, long buffer_len)
-{
-    LPMIDIHDR hdr = allocate_buffer(buffer_len);
-    if (!hdr) return pmInsufficientMemory;
-    pm_hosterror = midiInPrepareHeader(h, hdr, sizeof(MIDIHDR));
-    if (pm_hosterror) {
-        pm_free(hdr);
-        return pm_hosterror;
-    }
-    pm_hosterror = midiInAddBuffer(h, hdr, sizeof(MIDIHDR));
-    return pm_hosterror;
-}
-
-
-static PmError winmm_in_open(PmInternal *midi, void *driverInfo)
-{
-    DWORD dwDevice;
-    int i = midi->device_id;
-    int max_sysex_len = midi->buffer_len * 4;
-    int num_input_buffers = max_sysex_len / INPUT_SYSEX_LEN;
-    midiwinmm_type m;
-
-    dwDevice = (DWORD) descriptors[i].descriptor;
-
-    /* create system dependent device data */
-    m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */
-    midi->descriptor = m;
-    if (!m) goto no_memory;
-    m->handle.in = NULL;
-    m->buffers = NULL; /* not used for input */
-    m->num_buffers = 0; /* not used for input */
-    m->max_buffers = FALSE; /* not used for input */
-    m->buffers_expanded = 0; /* not used for input */
-    m->next_buffer = 0; /* not used for input */
-    m->buffer_signal = 0; /* not used for input */
-#ifdef USE_SYSEX_BUFFERS
-    for (i = 0; i < NUM_SYSEX_BUFFERS; i++) 
-        m->sysex_buffers[i] = NULL; /* not used for input */
-    m->next_sysex_buffer = 0; /* not used for input */
-#endif
-    m->last_time = 0;
-    m->first_message = TRUE; /* not used for input */
-    m->sysex_mode = FALSE;
-    m->sysex_word = 0;
-    m->sysex_byte_count = 0;
-    m->hdr = NULL; /* not used for input */
-    m->sync_time = 0;
-    m->delta = 0;
-    m->error = MMSYSERR_NOERROR;
-    /* 4000 is based on Windows documentation -- that's the value used in the
-       memory manager. It's small enough that it should not hurt performance even
-       if it's not optimal.
-     */
-    InitializeCriticalSectionAndSpinCount(&m->lock, 4000);
-    /* open device */
-    pm_hosterror = midiInOpen(&(m->handle.in),  /* input device handle */
-                              dwDevice,  /* device ID */
-                              (DWORD) winmm_in_callback,  /* callback address */
-                              (DWORD) midi,  /* callback instance data */
-                              CALLBACK_FUNCTION); /* callback is a procedure */
-    if (pm_hosterror) goto free_descriptor;
-
-    if (num_input_buffers < MIN_INPUT_BUFFERS)
-        num_input_buffers = MIN_INPUT_BUFFERS;
-    for (i = 0; i < num_input_buffers; i++) {
-        if (allocate_input_buffer(m->handle.in, INPUT_SYSEX_LEN)) {
-            /* either pm_hosterror was set, or the proper return code
-               is pmInsufficientMemory */
-            goto close_device;
-        }
-    }
-    /* start device */
-    pm_hosterror = midiInStart(m->handle.in);
-    if (pm_hosterror) goto reset_device;
-    return pmNoError;
-
-    /* undo steps leading up to the detected error */
-reset_device:
-    /* ignore return code (we already have an error to report) */
-    midiInReset(m->handle.in);
-close_device:
-    midiInClose(m->handle.in); /* ignore return code */
-free_descriptor:
-    midi->descriptor = NULL;
-    pm_free(m);
-no_memory:
-    if (pm_hosterror) {
-        int err = midiInGetErrorText(pm_hosterror, (char *) pm_hosterror_text,
-                                     PM_HOST_ERROR_MSG_LEN);
-        assert(err == MMSYSERR_NOERROR);
-        return pmHostError;
-    }
-    /* if !pm_hosterror, then the error must be pmInsufficientMemory */
-    return pmInsufficientMemory;
-    /* note: if we return an error code, the device will be
-       closed and memory will be freed. It's up to the caller
-       to free the parameter midi */
-}
-
-static PmError winmm_in_poll(PmInternal *midi) {
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    return m->error;
-}
-
-
-
-/* winmm_in_close -- close an open midi input device */
-/*
- * assume midi is non-null (checked by caller)
- */
-static PmError winmm_in_close(PmInternal *midi)
-{
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    if (!m) return pmBadPtr;
-    /* device to close */
-    if (pm_hosterror = midiInStop(m->handle.in)) {
-        midiInReset(m->handle.in); /* try to reset and close port */
-        midiInClose(m->handle.in);
-    } else if (pm_hosterror = midiInReset(m->handle.in)) {
-        midiInClose(m->handle.in); /* best effort to close midi port */
-    } else {
-        pm_hosterror = midiInClose(m->handle.in);
-    }
-    midi->descriptor = NULL;
-    DeleteCriticalSection(&m->lock);
-    pm_free(m); /* delete */
-    if (pm_hosterror) {
-        int err = midiInGetErrorText(pm_hosterror, (char *) pm_hosterror_text,
-                                     PM_HOST_ERROR_MSG_LEN);
-        assert(err == MMSYSERR_NOERROR);
-        return pmHostError;
-    }
-    return pmNoError;
-}
-
-
-/* Callback function executed via midiInput SW interrupt (via midiInOpen). */
-static void FAR PASCAL winmm_in_callback(
-    HMIDIIN hMidiIn,    /* midiInput device Handle */
-    WORD wMsg,          /* midi msg */
-    DWORD dwInstance,   /* application data */
-    DWORD dwParam1,     /* MIDI data */
-    DWORD dwParam2)    /* device timestamp (wrt most recent midiInStart) */
-{
-    static int entry = 0;
-    PmInternal *midi = (PmInternal *) dwInstance;
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-
-    /* NOTE: we do not just EnterCriticalSection() here because an
-     * MIM_CLOSE message arrives when the port is closed, but then
-     * the m->lock has been destroyed.
-     */
-
-    switch (wMsg) {
-    case MIM_DATA: {
-        /* if this callback is reentered with data, we're in trouble. 
-         * It's hard to imagine that Microsoft would allow callbacks 
-         * to be reentrant -- isn't the model that this is like a 
-         * hardware interrupt? -- but I've seen reentrant behavior 
-         * using a debugger, so it happens.
-         */
-        EnterCriticalSection(&m->lock);
-
-        /* dwParam1 is MIDI data received, packed into DWORD w/ 1st byte of
-                message LOB;
-           dwParam2 is time message received by input device driver, specified
-            in [ms] from when midiInStart called.
-           each message is expanded to include the status byte */
-
-        long new_driver_time = dwParam2;
-
-        if ((dwParam1 & 0x80) == 0) {
-            /* not a status byte -- ignore it. This happened running the
-               sysex.c test under Win2K with MidiMan USB 1x1 interface,
-               but I can't reproduce it. -RBD
-             */
-            /* printf("non-status byte found\n"); */
-        } else { /* data to process */
-            PmEvent event;
-            if (midi->time_proc)
-                dwParam2 = (*midi->time_proc)(midi->time_info);
-            event.timestamp = dwParam2;
-            event.message = dwParam1;
-            pm_read_short(midi, &event);
-        }
-        LeaveCriticalSection(&m->lock);
-        break;
-    }
-    case MIM_LONGDATA: {
-        MIDIHDR *lpMidiHdr = (MIDIHDR *) dwParam1;
-        unsigned char *data = (unsigned char *) lpMidiHdr->lpData;
-        unsigned int processed = 0;
-        int remaining = lpMidiHdr->dwBytesRecorded;
-
-        EnterCriticalSection(&m->lock);
-        /* printf("midi_in_callback -- lpMidiHdr %x, %d bytes, %2x...\n", 
-                lpMidiHdr, lpMidiHdr->dwBytesRecorded, *data); */
-        if (midi->time_proc)
-            dwParam2 = (*midi->time_proc)(midi->time_info);
-        /* can there be more than one message in one buffer? */
-        /* assume yes and iterate through them */
-        while (remaining > 0) {
-            unsigned int amt = pm_read_bytes(midi, data + processed, 
-                                             remaining, dwParam2);
-            remaining -= amt;
-            processed += amt;
-        }
-
-        /* when a device is closed, the pending MIM_LONGDATA buffers are
-           returned to this callback with dwBytesRecorded == 0. In this
-           case, we do not want to send them back to the interface (if
-           we do, the interface will not close, and Windows OS may hang). */
-        if (lpMidiHdr->dwBytesRecorded > 0) {
-            lpMidiHdr->dwBytesRecorded = 0;
-            lpMidiHdr->dwFlags = 0;
-			
-            /* note: no error checking -- can this actually fail? */
-            assert(midiInPrepareHeader(hMidiIn, lpMidiHdr, 
-                        sizeof(MIDIHDR)) == MMSYSERR_NOERROR);
-            /* note: I don't think this can fail except possibly for
-             * MMSYSERR_NOMEM, but the pain of reporting this
-             * unlikely but probably catastrophic error does not seem
-             * worth it.
-             */
-            assert(midiInAddBuffer(hMidiIn, lpMidiHdr, 
-                        sizeof(MIDIHDR)) == MMSYSERR_NOERROR);
-            LeaveCriticalSection(&m->lock);
-        } else {
-            midiInUnprepareHeader(hMidiIn,lpMidiHdr,sizeof(MIDIHDR));
-            LeaveCriticalSection(&m->lock);
-            pm_free(lpMidiHdr);
-        }
-        break;
-    }
-    case MIM_OPEN:
-        break;
-    case MIM_CLOSE:
-        break;
-    case MIM_ERROR:
-        /* printf("MIM_ERROR\n"); */
-        break;
-    case MIM_LONGERROR:
-        /* printf("MIM_LONGERROR\n"); */
-        break;
-    default:
-        break;
-    }
-}
-
-/*
-=========================================================================================
-begin midi output implementation
-=========================================================================================
-*/
-
-/* begin helper routines used by midiOutStream interface */
-
-/* add_to_buffer -- adds timestamped short msg to buffer, returns fullp */
-static int add_to_buffer(midiwinmm_type m, LPMIDIHDR hdr,
-                         unsigned long delta, unsigned long msg)
-{
-    unsigned long *ptr = (unsigned long *)
-                         (hdr->lpData + hdr->dwBytesRecorded);
-    *ptr++ = delta; /* dwDeltaTime */
-    *ptr++ = 0;     /* dwStream */
-    *ptr++ = msg;   /* dwEvent */
-    hdr->dwBytesRecorded += 3 * sizeof(long);
-    /* if the addition of three more words (a message) would extend beyond
-       the buffer length, then return TRUE (full)
-     */
-    return hdr->dwBytesRecorded + 3 * sizeof(long) > hdr->dwBufferLength;
-}
-
-
-static PmTimestamp pm_time_get(midiwinmm_type m)
-{
-    MMTIME mmtime;
-    MMRESULT wRtn;
-    mmtime.wType = TIME_TICKS;
-    mmtime.u.ticks = 0;
-    wRtn = midiStreamPosition(m->handle.stream, &mmtime, sizeof(mmtime));
-    assert(wRtn == MMSYSERR_NOERROR);
-    return mmtime.u.ticks;
-}
-
-
-/* end helper routines used by midiOutStream interface */
-
-
-static PmError winmm_out_open(PmInternal *midi, void *driverInfo)
-{
-    DWORD dwDevice;
-    int i = midi->device_id;
-    midiwinmm_type m;
-    MIDIPROPTEMPO propdata;
-    MIDIPROPTIMEDIV divdata;
-    int max_sysex_len = midi->buffer_len * 4;
-    int output_buffer_len;
-    int num_buffers;
-    dwDevice = (DWORD) descriptors[i].descriptor;
-
-    /* create system dependent device data */
-    m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */
-    midi->descriptor = m;
-    if (!m) goto no_memory;
-    m->handle.out = NULL;
-    m->buffers = NULL;
-    m->num_buffers = 0;
-    m->max_buffers = 0;
-    m->buffers_expanded = FALSE;
-    m->next_buffer = 0;
-#ifdef USE_SYSEX_BUFFERS
-    m->sysex_buffers[0] = NULL;
-    m->sysex_buffers[1] = NULL;
-    m->next_sysex_buffer = 0;
-#endif
-    m->last_time = 0;
-    m->first_message = TRUE; /* we treat first message as special case */
-    m->sysex_mode = FALSE;
-    m->sysex_word = 0;
-    m->sysex_byte_count = 0;
-    m->hdr = NULL;
-    m->sync_time = 0;
-    m->delta = 0;
-    m->error = MMSYSERR_NOERROR;
-
-    /* create a signal */
-    m->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL);
-
-    /* this should only fail when there are very serious problems */
-    assert(m->buffer_signal);
-
-    /* open device */
-    if (midi->latency == 0) {
-        /* use simple midi out calls */
-        pm_hosterror = midiOutOpen((LPHMIDIOUT) & m->handle.out,  /* device Handle */
-                                   dwDevice,  /* device ID  */
-                                   /* note: same callback fn as for StreamOpen: */
-                                   (DWORD) winmm_streamout_callback, /* callback fn */
-                                   (DWORD) midi,  /* callback instance data */
-                                   CALLBACK_FUNCTION); /* callback type */
-    } else {
-        /* use stream-based midi output (schedulable in future) */
-        pm_hosterror = midiStreamOpen(&m->handle.stream,  /* device Handle */
-                                      (LPUINT) & dwDevice,  /* device ID pointer */
-                                      1,  /* reserved, must be 1 */
-                                      (DWORD) winmm_streamout_callback,
-                                      (DWORD) midi,  /* callback instance data */
-                                      CALLBACK_FUNCTION);
-    }
-    if (pm_hosterror != MMSYSERR_NOERROR) {
-        goto free_descriptor;
-    }
-
-    if (midi->latency == 0) {
-        num_buffers = NUM_SIMPLE_SYSEX_BUFFERS;
-        output_buffer_len = max_sysex_len / num_buffers;
-        if (output_buffer_len < MIN_SIMPLE_SYSEX_LEN)
-            output_buffer_len = MIN_SIMPLE_SYSEX_LEN;
-    } else {
-        long dur = 0;
-        num_buffers = max(midi->buffer_len, midi->latency / 2);
-        if (num_buffers < MIN_STREAM_BUFFERS)
-            num_buffers = MIN_STREAM_BUFFERS;
-        output_buffer_len = STREAM_BUFFER_LEN;
-
-        propdata.cbStruct = sizeof(MIDIPROPTEMPO);
-        propdata.dwTempo = 480000; /* microseconds per quarter */
-        pm_hosterror = midiStreamProperty(m->handle.stream,
-                                          (LPBYTE) & propdata,
-                                          MIDIPROP_SET | MIDIPROP_TEMPO);
-        if (pm_hosterror) goto close_device;
-
-        divdata.cbStruct = sizeof(MIDIPROPTEMPO);
-        divdata.dwTimeDiv = 480;   /* divisions per quarter */
-        pm_hosterror = midiStreamProperty(m->handle.stream,
-                                          (LPBYTE) & divdata,
-                                          MIDIPROP_SET | MIDIPROP_TIMEDIV);
-        if (pm_hosterror) goto close_device;
-    }
-    /* allocate buffers */
-    if (allocate_buffers(m, output_buffer_len, num_buffers)) 
-        goto free_buffers;
-    /* start device */
-    if (midi->latency != 0) {
-        pm_hosterror = midiStreamRestart(m->handle.stream);
-        if (pm_hosterror != MMSYSERR_NOERROR) goto free_buffers;
-    }
-    return pmNoError;
-
-free_buffers:
-    /* buffers are freed below by winmm_out_delete */
-close_device:
-    midiOutClose(m->handle.out);
-free_descriptor:
-    midi->descriptor = NULL;
-    winmm_out_delete(midi); /* frees buffers and m */
-no_memory:
-    if (pm_hosterror) {
-        int err = midiOutGetErrorText(pm_hosterror, (char *) pm_hosterror_text,
-                                      PM_HOST_ERROR_MSG_LEN);
-        assert(err == MMSYSERR_NOERROR);
-        return pmHostError;
-    }
-    return pmInsufficientMemory;
-}
-
-
-/* winmm_out_delete -- carefully free data associated with midi */
-/**/
-static void winmm_out_delete(PmInternal *midi)
-{
-    int i;
-    /* delete system dependent device data */
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    if (m) {
-        if (m->buffer_signal) {
-            /* don't report errors -- better not to stop cleanup */
-            CloseHandle(m->buffer_signal);
-        }
-        /* if using stream output, free buffers */
-        for (i = 0; i < m->num_buffers; i++) {
-            if (m->buffers[i]) pm_free(m->buffers[i]);
-        }
-        m->num_buffers = 0;
-        pm_free(m->buffers);
-        m->max_buffers = 0;
-#ifdef USE_SYSEX_BUFFERS
-        /* free sysex buffers */
-        for (i = 0; i < NUM_SYSEX_BUFFERS; i++) {
-            if (m->sysex_buffers[i]) pm_free(m->sysex_buffers[i]);
-        }
-#endif
-    }
-    midi->descriptor = NULL;
-    pm_free(m); /* delete */
-}
-
-
-/* see comments for winmm_in_close */
-static PmError winmm_out_close(PmInternal *midi)
-{
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    if (m->handle.out) {
-        /* device to close */
-        if (midi->latency == 0) {
-            pm_hosterror = midiOutClose(m->handle.out);
-        } else {
-            pm_hosterror = midiStreamClose(m->handle.stream);
-        }
-        /* regardless of outcome, free memory */
-        winmm_out_delete(midi);
-    }
-    if (pm_hosterror) {
-        int err = midiOutGetErrorText(pm_hosterror,
-                                      (char *) pm_hosterror_text,
-                                      PM_HOST_ERROR_MSG_LEN);
-        assert(err == MMSYSERR_NOERROR);
-        return pmHostError;
-    }
-    return pmNoError;
-}
-
-
-static PmError winmm_out_abort(PmInternal *midi)
-{
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    m->error = MMSYSERR_NOERROR;
-
-    /* only stop output streams */
-    if (midi->latency > 0) {
-        m->error = midiStreamStop(m->handle.stream);
-    }
-    return m->error ? pmHostError : pmNoError;
-}
-
-
-static PmError winmm_write_flush(PmInternal *midi, PmTimestamp timestamp)
-{
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    assert(m);
-    if (m->hdr) {
-        m->error = midiOutPrepareHeader(m->handle.out, m->hdr, 
-                                        sizeof(MIDIHDR));
-        if (m->error) {
-            /* do not send message */
-        } else if (midi->latency == 0) {
-            /* As pointed out by Nigel Brown, 20Sep06, dwBytesRecorded
-             * should be zero. This is set in get_free_sysex_buffer(). 
-             * The msg length goes in dwBufferLength in spite of what
-             * Microsoft documentation says (or doesn't say). */
-            m->hdr->dwBufferLength = m->hdr->dwBytesRecorded;
-            m->hdr->dwBytesRecorded = 0;
-            m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR));
-        } else {
-            m->error = midiStreamOut(m->handle.stream, m->hdr, 
-                                     sizeof(MIDIHDR));
-        }
-        midi->fill_base = NULL;
-        m->hdr = NULL;
-        if (m->error) {
-            m->hdr->dwFlags = 0; /* release the buffer */
-            return pmHostError;
-        }
-    }
-    return pmNoError;
-}
-
-
-
-#ifdef GARBAGE
-static PmError winmm_write_sysex_byte(PmInternal *midi, unsigned char byte)
-{
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    unsigned char *msg_buffer;
-
-    /* at the beginning of sysex, m->hdr is NULL */
-    if (!m->hdr) { /* allocate a buffer if none allocated yet */
-        m->hdr = get_free_output_buffer(midi);
-        if (!m->hdr) return pmInsufficientMemory;
-        m->sysex_byte_count = 0;
-    }
-    /* figure out where to write byte */
-    msg_buffer = (unsigned char *) (m->hdr->lpData);
-    assert(m->hdr->lpData == (char *) (m->hdr + 1));
-
-    /* check for overflow */
-    if (m->sysex_byte_count >= m->hdr->dwBufferLength) {
-        /* allocate a bigger message -- double it every time */
-        LPMIDIHDR big = allocate_buffer(m->sysex_byte_count * 2);
-        /* printf("expand to %d bytes\n", m->sysex_byte_count * 2); */
-        if (!big) return pmInsufficientMemory;
-        m->error = midiOutPrepareHeader(m->handle.out, big,
-                                        sizeof(MIDIHDR));
-        if (m->error) {
-            m->hdr = NULL;
-            return pmHostError;
-        }
-        memcpy(big->lpData, msg_buffer, m->sysex_byte_count);
-        msg_buffer = (unsigned char *) (big->lpData);
-        if (m->buffers[0] == m->hdr) {
-            m->buffers[0] = big;
-            pm_free(m->hdr);
-            /* printf("freed m->hdr\n"); */
-        } else if (m->buffers[1] == m->hdr) {
-            m->buffers[1] = big;
-            pm_free(m->hdr);
-            /* printf("freed m->hdr\n"); */
-        }
-        m->hdr = big;
-    }
-
-    /* append byte to message */
-    msg_buffer[m->sysex_byte_count++] = byte;
-
-    /* see if we have a complete message */
-    if (byte == MIDI_EOX) {
-        m->hdr->dwBytesRecorded = m->sysex_byte_count;
-        /*
-        { int i; int len = m->hdr->dwBytesRecorded;
-          printf("OutLongMsg %d ", len);
-          for (i = 0; i < len; i++) {
-              printf("%2x ", msg_buffer[i]);
-          }
-        }
-        */
-        m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR));
-        m->hdr = NULL; /* stop using this message buffer */
-        if (m->error) return pmHostError;
-    }
-    return pmNoError;
-}
-#endif
-
-
-static PmError winmm_write_short(PmInternal *midi, PmEvent *event)
-{
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    PmError rslt = pmNoError;
-    assert(m);
-
-    if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */
-        m->error = midiOutShortMsg(m->handle.out, event->message);
-        if (m->error) rslt = pmHostError;
-    } else {  /* use midiStream interface -- pass data through buffers */
-        unsigned long when = event->timestamp;
-        unsigned long delta;
-        int full;
-        if (when == 0) when = midi->now;
-        /* when is in real_time; translate to intended stream time */
-        when = when + m->delta + midi->latency;
-        /* make sure we don't go backward in time */
-        if (when < m->last_time) when = m->last_time;
-        delta = when - m->last_time;
-        m->last_time = when;
-        /* before we insert any data, we must have a buffer */
-        if (m->hdr == NULL) {
-            /* stream interface: buffers allocated when stream is opened */
-            m->hdr = get_free_output_buffer(midi);
-        }
-        full = add_to_buffer(m, m->hdr, delta, event->message);
-        if (full) rslt = winmm_write_flush(midi, when);
-    }
-    return rslt;
-}
-
-#define winmm_begin_sysex winmm_write_flush
-#ifndef winmm_begin_sysex
-static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp)
-{
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    PmError rslt = pmNoError;
-
-    if (midi->latency == 0) {
-        /* do nothing -- it's handled in winmm_write_byte */
-    } else {
-        /* sysex expects an empty sysex buffer, so send whatever is here */
-        rslt = winmm_write_flush(midi);
-    }
-    return rslt;
-}
-#endif
-
-static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp)
-{
-    /* could check for callback_error here, but I haven't checked
-     * what happens if we exit early and don't finish the sysex msg
-     * and clean up
-     */
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    PmError rslt = pmNoError;
-    LPMIDIHDR hdr = m->hdr;
-    if (!hdr) return rslt; /* something bad happened earlier,
-            do not report an error because it would have been 
-            reported (at least) once already */
-    /* a(n old) version of MIDI YOKE requires a zero byte after
-     * the sysex message, but do not increment dwBytesRecorded: */
-    hdr->lpData[hdr->dwBytesRecorded] = 0;
-    if (midi->latency == 0) {
-#ifdef DEBUG_PRINT_BEFORE_SENDING_SYSEX
-        /* DEBUG CODE: */
-        { int i; int len = m->hdr->dwBufferLength;
-          printf("OutLongMsg %d ", len);
-          for (i = 0; i < len; i++) {
-              printf("%2x ", (unsigned char) (m->hdr->lpData[i]));
-          }
-        }
-#endif
-    } else {
-        /* Using stream interface. There are accumulated bytes in m->hdr
-           to send using midiStreamOut
-         */
-        /* add bytes recorded to MIDIEVENT length, but don't
-           count the MIDIEVENT data (3 longs) */
-        MIDIEVENT *evt = (MIDIEVENT *) (hdr->lpData);
-        evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long);
-        /* round up BytesRecorded to multiple of 4 */
-        hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3;
-    }
-    rslt = winmm_write_flush(midi, timestamp);
-    return rslt;
-}
-
-
-static PmError winmm_write_byte(PmInternal *midi, unsigned char byte,
-                                PmTimestamp timestamp)
-{
-    /* write a sysex byte */
-    PmError rslt = pmNoError;
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    LPMIDIHDR hdr = m->hdr;
-    unsigned char *msg_buffer;
-    assert(m);
-    if (!hdr) {
-        m->hdr = hdr = get_free_output_buffer(midi);
-        assert(hdr);
-        midi->fill_base = (unsigned char *) m->hdr->lpData;
-        midi->fill_offset_ptr = &(hdr->dwBytesRecorded);
-        /* when buffer fills, Pm_WriteSysEx will revert to calling
-         * pmwin_write_byte, which expect to have space, so leave
-         * one byte free for pmwin_write_byte. Leave another byte
-         * of space for zero after message to make early version of 
-         * MIDI YOKE driver happy -- therefore dwBufferLength - 2 */
-        midi->fill_length = hdr->dwBufferLength - 2;
-        if (midi->latency != 0) {
-            unsigned long when = (unsigned long) timestamp;
-            unsigned long delta;
-            unsigned long *ptr;
-            if (when == 0) when = midi->now;
-            /* when is in real_time; translate to intended stream time */
-            when = when + m->delta + midi->latency;
-            /* make sure we don't go backward in time */
-            if (when < m->last_time) when = m->last_time;
-            delta = when - m->last_time;
-            m->last_time = when;
-
-            ptr = (unsigned long *) hdr->lpData;
-            *ptr++ = delta;
-            *ptr++ = 0;
-            *ptr = MEVT_F_LONG;
-            hdr->dwBytesRecorded = 3 * sizeof(long);
-            /* data will be added at an offset of dwBytesRecorded ... */
-        }
-    }
-    /* add the data byte */
-    msg_buffer = (unsigned char *) (hdr->lpData);
-    msg_buffer[hdr->dwBytesRecorded++] = byte;
-
-    /* see if buffer is full, leave one byte extra for pad */
-    if (hdr->dwBytesRecorded >= hdr->dwBufferLength - 1) {
-        /* write what we've got and continue */
-        rslt = winmm_end_sysex(midi, timestamp); 
-    }
-    return rslt;
-}
-
-#ifdef EXPANDING_SYSEX_BUFFERS
-note: this code is here as an aid in case you want sysex buffers
-      to expand to hold large messages completely. If so, you
-      will want to change SYSEX_BYTES_PER_BUFFER above to some
-      variable that remembers the buffer size. A good place to 
-      put this value would be in the hdr->dwUser field.
-
-            rslt = resize_sysex_buffer(midi, m->sysex_byte_count, 
-                                       m->sysex_byte_count * 2);
-
-            if (rslt == pmBufferMaxSize) /* if the buffer can't be resized */
-#endif
-#ifdef EXPANDING_SYSEX_BUFFERS
-            int bytesRecorded = hdr->dwBytesRecorded; /* this field gets wiped out, so we'll save it */
-            rslt = resize_sysex_buffer(midi, bytesRecorded, 2 * bytesRecorded);
-            hdr->dwBytesRecorded = bytesRecorded;
-
-            if (rslt == pmBufferMaxSize) /* if buffer can't be resized */
-#endif
-
-
-
-static PmTimestamp winmm_synchronize(PmInternal *midi)
-{
-    midiwinmm_type m;
-    unsigned long pm_stream_time_2;
-    unsigned long real_time;
-    unsigned long pm_stream_time;
-
-    /* only synchronize if we are using stream interface */
-    if (midi->latency == 0) return 0;
-
-    /* figure out the time */
-    m = (midiwinmm_type) midi->descriptor;
-    pm_stream_time_2 = pm_time_get(m);
-
-    do {
-        /* read real_time between two reads of stream time */
-        pm_stream_time = pm_stream_time_2;
-        real_time = (*midi->time_proc)(midi->time_info);
-        pm_stream_time_2 = pm_time_get(m);
-        /* repeat if more than 1ms elapsed */
-    } while (pm_stream_time_2 > pm_stream_time + 1);
-    m->delta = pm_stream_time - real_time;
-    m->sync_time = real_time;
-    return real_time;
-}
-
-#ifdef USE_SYSEX_BUFFERS
-/* winmm_out_callback -- recycle sysex buffers */
-static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg,
-                                        DWORD dwInstance, DWORD dwParam1, 
-                                        DWORD dwParam2)
-{
-    PmInternal *midi = (PmInternal *) dwInstance;
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    LPMIDIHDR hdr = (LPMIDIHDR) dwParam1;
-    int err = 0;  /* set to 0 so that no buffer match will also be an error */
-
-    /* Future optimization: eliminate UnprepareHeader calls -- they aren't
-       necessary; however, this code uses the prepared-flag to indicate which
-       buffers are free, so we need to do something to flag empty buffers if
-       we leave them prepared
-     */
-    printf("out_callback: hdr %x, wMsg %x, MOM_DONE %x\n", 
-           hdr, wMsg, MOM_DONE);
-    if (wMsg == MOM_DONE)
-    assert(midiOutUnprepareHeader(m->handle.out, hdr,
-                        sizeof(MIDIHDR)) == MMSYSERR_NOERROR);
-    /* notify waiting sender that a buffer is available */
-    err = SetEvent(m->buffer_signal);
-    assert(err); /* false -> error */
-}
-#endif
-
-/* winmm_streamout_callback -- unprepare (free) buffer header */
-static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,
-        DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
-{
-    PmInternal *midi = (PmInternal *) dwInstance;
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    LPMIDIHDR hdr = (LPMIDIHDR) dwParam1;
-    int err;
-
-    /* Even if an error is pending, I think we should unprepare msgs and
-       signal their arrival
-     */
-    /* printf("streamout_callback: hdr %x, wMsg %x, MOM_DONE %x\n", 
-           hdr, wMsg, MOM_DONE); */
-    if (wMsg == MOM_DONE) {
-        assert(midiOutUnprepareHeader(m->handle.out, hdr, 
-                    sizeof(MIDIHDR)) == MMSYSERR_NOERROR);
-    }
-    /* signal client in case it is blocked waiting for buffer */
-    err = SetEvent(m->buffer_signal);
-    assert(err); /* false -> error */
-}
-
-
-/*
-=========================================================================================
-begin exported functions
-=========================================================================================
-*/
-
-#define winmm_in_abort pm_fail_fn
-pm_fns_node pm_winmm_in_dictionary = {
-                                         none_write_short,
-                                         none_sysex,
-                                         none_sysex,
-                                         none_write_byte,
-                                         none_write_short,
-                                         none_write_flush,
-                                         winmm_synchronize,
-                                         winmm_in_open,
-                                         winmm_in_abort,
-                                         winmm_in_close,
-                                         winmm_in_poll,
-                                         winmm_has_host_error,
-                                         winmm_get_host_error
-                                     };
-
-pm_fns_node pm_winmm_out_dictionary = {
-                                          winmm_write_short,
-                                          winmm_begin_sysex,
-                                          winmm_end_sysex,
-                                          winmm_write_byte,
-                                          winmm_write_short,  /* short realtime message */
-                                          winmm_write_flush,
-                                          winmm_synchronize,
-                                          winmm_out_open,
-                                          winmm_out_abort,
-                                          winmm_out_close,
-                                          none_poll,
-                                          winmm_has_host_error,
-                                          winmm_get_host_error
-                                      };
-
-
-/* initialize winmm interface. Note that if there is something wrong
-   with winmm (e.g. it is not supported or installed), it is not an
-   error. We should simply return without having added any devices to
-   the table. Hence, no error code is returned. Furthermore, this init
-   code is called along with every other supported interface, so the
-   user would have a very hard time figuring out what hardware and API
-   generated the error. Finally, it would add complexity to pmwin.c to
-   remember where the error code came from in order to convert to text.
- */
-void pm_winmm_init( void )
-{
-    pm_winmm_mapper_input();
-    pm_winmm_mapper_output();
-    pm_winmm_general_inputs();
-    pm_winmm_general_outputs();
-}
-
-
-/* no error codes are returned, even if errors are encountered, because
-   there is probably nothing the user could do (e.g. it would be an error
-   to retry.
- */
-void pm_winmm_term( void )
-{
-    int i;
-#ifdef DEBUG
-    char msg[PM_HOST_ERROR_MSG_LEN];
-#endif
-    int doneAny = 0;
-#ifdef DEBUG
-    printf("pm_winmm_term called\n");
-#endif
-    for (i = 0; i < pm_descriptor_index; i++) {
-        PmInternal * midi = descriptors[i].internalDescriptor;
-        if (midi) {
-            midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-            if (m->handle.out) {
-                /* close next open device*/
-#ifdef DEBUG
-                if (doneAny == 0) {
-                    printf("begin closing open devices...\n");
-                    doneAny = 1;
-                }
-                /* report any host errors; this EXTEREMELY useful when
-                   trying to debug client app */
-                if (winmm_has_host_error(midi)) {
-                    winmm_get_host_error(midi, msg, PM_HOST_ERROR_MSG_LEN);
-                    printf("%s\n", msg);
-                }
-#endif
-                /* close all open ports */
-                (*midi->dictionary->close)(midi);
-            }
-        }
-    }
-    if (midi_in_caps) {
-        pm_free(midi_in_caps);
-        midi_in_caps = NULL;
-    }
-    if (midi_out_caps) {
-        pm_free(midi_out_caps);
-        midi_out_caps = NULL;
-    }
-#ifdef DEBUG
-    if (doneAny) {
-        printf("warning: devices were left open. They have been closed.\n");
-    }
-    printf("pm_winmm_term exiting\n");
-#endif
-    pm_descriptor_index = 0;
-}
+/* pmwinmm.c -- system specific definitions */++#ifndef _WIN32_WINNT+    /* without this define, InitializeCriticalSectionAndSpinCount is +     * undefined. This version level means "Windows 2000 and higher" +     */+    #define _WIN32_WINNT 0x0500+#endif++#define UNICODE 1+#include <wchar.h>+#include "windows.h"+#include "mmsystem.h"+#include "portmidi.h"+#include "pmutil.h"+#include "pminternal.h"+#include "pmwinmm.h"+#include <string.h>+#include "porttime.h"+#ifndef UNICODE+#error Expected UNICODE to be defined+#endif+++/* asserts used to verify portMidi code logic is sound; later may want+    something more graceful */+#include <assert.h>+#ifdef MMDEBUG+/* this printf stuff really important for debugging client app w/host errors.+    probably want to do something else besides read/write from/to console+    for portability, however */+#define STRING_MAX 80+#include "stdio.h"+#endif++#define streql(x, y) (strcmp(x, y) == 0)++#define MIDI_SYSEX      0xf0+#define MIDI_EOX        0xf7++/* callback routines */+static void CALLBACK winmm_in_callback(HMIDIIN hMidiIn,+                                       UINT wMsg, DWORD_PTR dwInstance, +                                       DWORD_PTR dwParam1, DWORD_PTR dwParam2);+static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,+                                              DWORD_PTR dwInstance, +                                              DWORD_PTR dwParam1,+                                              DWORD_PTR dwParam2);++extern pm_fns_node pm_winmm_in_dictionary;+extern pm_fns_node pm_winmm_out_dictionary;++static void winmm_out_delete(PmInternal *midi); /* forward reference */++/*+A note about buffers: WinMM seems to hold onto buffers longer than+one would expect, e.g. when I tried using 2 small buffers to send+long sysex messages, at some point WinMM held both buffers. This problem+was fixed by making buffers bigger. Therefore, it seems that there should +be enough buffer space to hold a whole sysex message. ++The bufferSize passed into Pm_OpenInput (passed into here as buffer_len)+will be used to estimate the largest sysex message (= buffer_len * 4 bytes).+Call that the max_sysex_len = buffer_len * 4.++For simple midi output (latency == 0), allocate 3 buffers, each with half+the size of max_sysex_len, but each at least 256 bytes.++For stream output, there will already be enough space in very short+buffers, so use them, but make sure there are at least 16.++For input, use many small buffers rather than 2 large ones so that when +there are short sysex messages arriving frequently (as in control surfaces)+there will be more free buffers to fill. Use max_sysex_len / 64 buffers,+but at least 16, of size 64 bytes each.++The following constants help to represent these design parameters:+*/+#define NUM_SIMPLE_SYSEX_BUFFERS 3+#define MIN_SIMPLE_SYSEX_LEN 256++#define MIN_STREAM_BUFFERS 16+#define STREAM_BUFFER_LEN 24++#define INPUT_SYSEX_LEN 64+#define MIN_INPUT_BUFFERS 16++/* if we run out of space for output (assume this is due to a sysex msg,+   expand by up to NUM_EXPANSION_BUFFERS in increments of EXPANSION_BUFFER_LEN+ */+#define NUM_EXPANSION_BUFFERS 128+#define EXPANSION_BUFFER_LEN 1024++/* A sysex buffer has 3 DWORDS as a header plus the actual message size */+#define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(long)*3)+/* A MIDIHDR with a sysex message is the buffer length plus the header size */+#define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_BUFFER_LENGTH(x) + sizeof(MIDIHDR))++/*+==============================================================================+win32 mmedia system specific structure passed to midi callbacks+==============================================================================+*/++/* global winmm device info */+MIDIINCAPS *midi_in_caps = NULL;+MIDIINCAPS midi_in_mapper_caps;+UINT midi_num_inputs = 0;+MIDIOUTCAPS *midi_out_caps = NULL;+MIDIOUTCAPS midi_out_mapper_caps;+UINT midi_num_outputs = 0;++/* per device info */+typedef struct winmm_info_struct {+    union {+        HMIDISTRM stream;   /* windows handle for stream */+        HMIDIOUT out;       /* windows handle for out calls */+        HMIDIIN in;         /* windows handle for in calls */+    } handle;++    /* midi output messages are sent in these buffers, which are allocated+     * in a round-robin fashion, using next_buffer as an index+     */+    LPMIDIHDR *buffers;     /* pool of buffers for midi in or out data */+    int max_buffers;        /* length of buffers array */+    int buffers_expanded;   /* buffers array expanded for extra msgs? */+    int num_buffers;        /* how many buffers allocated in buffers array */+    int next_buffer;        /* index of next buffer to send */+    HANDLE buffer_signal;   /* used to wait for buffer to become free */+    unsigned long last_time;    /* last output time */+    int first_message;          /* flag: treat first message differently */+    int sysex_mode;             /* middle of sending sysex */+    unsigned long sysex_word;   /* accumulate data when receiving sysex */+    unsigned int sysex_byte_count; /* count how many received */+    LPMIDIHDR hdr;              /* the message accumulating sysex to send */+    unsigned long sync_time;    /* when did we last determine delta? */+    long delta;                 /* difference between stream time and+                                       real time */+    CRITICAL_SECTION lock;      /* prevents reentrant callbacks (input only) */+} winmm_info_node, *winmm_info_type;+++/*+=============================================================================+general MIDI device queries+=============================================================================+*/++/* add a device after converting device (product) name to UTF-8 */+static void pm_add_device_w(char *api, WCHAR *device_name, int is_input,+                            int is_virtual, void *descriptor, pm_fns_type dictionary)+{+    char utf8name[4 * MAXPNAMELEN];+    WideCharToMultiByte(CP_UTF8, 0, device_name, -1, +                        utf8name, 4 * MAXPNAMELEN - 1, NULL, NULL);+    /* ignore errors here -- if pm_descriptor_max is exceeded, +       some devices will not be accessible. */+    pm_add_device(api, utf8name, is_input, is_virtual, descriptor, dictionary);+}+++static void pm_winmm_general_inputs(void)+{+    UINT i;+    WORD wRtn;+    midi_num_inputs = midiInGetNumDevs();+    midi_in_caps = (MIDIINCAPS *) pm_alloc(sizeof(MIDIINCAPS) * +                                           midi_num_inputs);+    if (midi_in_caps == NULL) {+        /* if you can't open a particular system-level midi interface+         * (such as winmm), we just consider that system or API to be+         * unavailable and move on without reporting an error.+         */+        return;+    }++    for (i = 0; i < midi_num_inputs; i++) {+        wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) & midi_in_caps[i],+                                sizeof(MIDIINCAPS));+        if (wRtn == MMSYSERR_NOERROR) {+            pm_add_device_w("MMSystem", midi_in_caps[i].szPname, TRUE, FALSE,+                              (void *) (intptr_t) i, &pm_winmm_in_dictionary);+        }+    }+}+++static void pm_winmm_mapper_input(void)+{+    WORD wRtn;+    /* Note: if MIDIMAPPER opened as input (documentation implies you+        can, but current system fails to retrieve input mapper+        capabilities) then you still should retrieve some form of+        setup info. */+    wRtn = midiInGetDevCaps((UINT) MIDIMAPPER,+                            (LPMIDIINCAPS) & midi_in_mapper_caps, +                            sizeof(MIDIINCAPS));+    if (wRtn == MMSYSERR_NOERROR) {+        pm_add_device_w("MMSystem", midi_in_mapper_caps.szPname, TRUE, FALSE,+                        (void *) (intptr_t) MIDIMAPPER, +                        &pm_winmm_in_dictionary);+    }+}+++static void pm_winmm_general_outputs(void)+{+    UINT i;+    DWORD wRtn;+    midi_num_outputs = midiOutGetNumDevs();+    midi_out_caps = pm_alloc(sizeof(MIDIOUTCAPS) * midi_num_outputs);++    if (midi_out_caps == NULL) {+        /* no error is reported -- see pm_winmm_general_inputs */+        return ;+    }++    for (i = 0; i < midi_num_outputs; i++) {+        wRtn = midiOutGetDevCaps(i, (LPMIDIOUTCAPS) & midi_out_caps[i],+                                 sizeof(MIDIOUTCAPS));+        if (wRtn == MMSYSERR_NOERROR) {+            pm_add_device_w("MMSystem", midi_out_caps[i].szPname, FALSE, FALSE,+                            (void *) (intptr_t) i, &pm_winmm_out_dictionary);+        }+    }+}+++static void pm_winmm_mapper_output(void)+{+    WORD wRtn;+    /* Note: if MIDIMAPPER opened as output (pseudo MIDI device+        maps device independent messages into device dependant ones,+        via NT midimapper program) you still should get some setup info */+    wRtn = midiOutGetDevCaps((UINT) MIDIMAPPER, (LPMIDIOUTCAPS)+                             & midi_out_mapper_caps, sizeof(MIDIOUTCAPS));+    if (wRtn == MMSYSERR_NOERROR) {+        pm_add_device_w("MMSystem", midi_out_mapper_caps.szPname, FALSE, FALSE,+                        (void *) (intptr_t) MIDIMAPPER, +                        &pm_winmm_out_dictionary);+    }+}+++/*+============================================================================+host error handling+============================================================================+*/++static unsigned int winmm_check_host_error(PmInternal *midi)+{+    return FALSE;+}+++/*+=============================================================================+buffer handling+=============================================================================+*/+static MIDIHDR *allocate_buffer(long data_size)+{+    LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size));+    MIDIEVENT *evt;+    if (!hdr) return NULL;+    evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */+    hdr->lpData = (LPSTR) evt;+    hdr->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size);+    hdr->dwBytesRecorded = 0;+    hdr->dwFlags = 0;+    hdr->dwUser = hdr->dwBufferLength;+    return hdr;+}+++static PmError allocate_buffers(winmm_info_type info, long data_size, +                                long count)+{+    int i;+    /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */+    info->num_buffers = 0; /* in case no memory can be allocated */+    info->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count);+    if (!info->buffers) return pmInsufficientMemory;+    info->max_buffers = count;+    for (i = 0; i < count; i++) {+        LPMIDIHDR hdr = allocate_buffer(data_size);+        if (!hdr) { /* free everything allocated so far and return */+            for (i = i - 1; i >= 0; i--) pm_free(info->buffers[i]);+            pm_free(info->buffers);+            info->max_buffers = 0;+            return pmInsufficientMemory;+        }+        info->buffers[i] = hdr; /* this may be NULL if allocation fails */+    }+    info->num_buffers = count;+    return pmNoError;+}+++static LPMIDIHDR get_free_output_buffer(PmInternal *midi)+{+    LPMIDIHDR r = NULL;+    winmm_info_type info = (winmm_info_type) midi->api_info;+    while (TRUE) {+        int i;+        for (i = 0; i < info->num_buffers; i++) {+            /* cycle through buffers, modulo info->num_buffers */+            info->next_buffer++;+            if (info->next_buffer >= info->num_buffers) info->next_buffer = 0;+            r = info->buffers[info->next_buffer];+            if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer;+        }+        /* after scanning every buffer and not finding anything, block */+        if (WaitForSingleObject(info->buffer_signal, 1000) == WAIT_TIMEOUT) {+#ifdef MMDEBUG+            printf("PortMidi warning: get_free_output_buffer() "+                   "wait timed out after 1000ms\n");+#endif+            /* if we're trying to send a sysex message, maybe the +             * message is too big and we need more message buffers.+             * Expand the buffer pool by 128KB using 1024-byte buffers.+             */+            /* first, expand the buffers array if necessary */+            if (!info->buffers_expanded) {+                LPMIDIHDR *new_buffers = (LPMIDIHDR *) pm_alloc(+                               (info->num_buffers + NUM_EXPANSION_BUFFERS) *+                               sizeof(LPMIDIHDR));+                /* if no memory, we could return a no-memory error, but user+                 * probably will be unprepared to deal with it. Maybe the+                 * MIDI driver is temporarily hung so we should just wait.+                 * I don't know the right answer, but waiting is easier.+                 */+                if (!new_buffers) continue;+                /* copy buffers to new_buffers and replace buffers */+                memcpy(new_buffers, info->buffers, +                       info->num_buffers * sizeof(LPMIDIHDR));+                pm_free(info->buffers);+                info->buffers = new_buffers;+                info->max_buffers = info->num_buffers + NUM_EXPANSION_BUFFERS;+                info->buffers_expanded = TRUE;+            }+            /* next, add one buffer and return it */+            if (info->num_buffers < info->max_buffers) {+                r = allocate_buffer(EXPANSION_BUFFER_LEN);+                /* again, if there's no memory, we may not really be +                 * dead -- maybe the system is temporarily hung and+                 * we can just wait longer for a message buffer */+                if (!r) continue;+                info->buffers[info->num_buffers++] = r;+                goto found_buffer; /* break out of 2 loops */+            }+            /* else, we've allocated all NUM_EXPANSION_BUFFERS buffers,+             * and we have no free buffers to send. We'll just keep+             * polling to see if any buffers show up.+             */+        }+    }+found_buffer:+    r->dwBytesRecorded = 0;+    /* actual buffer length is saved in dwUser field */+    r->dwBufferLength = (DWORD) r->dwUser;+    return r;+}++/*+============================================================================+begin midi input implementation+============================================================================+*/+++static unsigned int allocate_input_buffer(HMIDIIN h, long buffer_len)+{+    LPMIDIHDR hdr = allocate_buffer(buffer_len);+    if (!hdr) return pmInsufficientMemory;+    /* note: pm_hosterror is normally a boolean, but here, we store Win+     * error code. The caller must test the value for nonzero, set+     * pm_hosterror_text, and then set pm_hosterror to TRUE */+    pm_hosterror = midiInPrepareHeader(h, hdr, sizeof(MIDIHDR));+    if (pm_hosterror) {+        pm_free(hdr);+        return pm_hosterror;+    }+    pm_hosterror = midiInAddBuffer(h, hdr, sizeof(MIDIHDR));+    return pm_hosterror;+}+++static winmm_info_type winmm_info_create(void)+{+    winmm_info_type info = (winmm_info_type) pm_alloc(sizeof(winmm_info_node));+    info->handle.in = NULL;+    info->handle.out = NULL;+    info->buffers = NULL; /* not used for input */+    info->num_buffers = 0; /* not used for input */+    info->max_buffers = 0; /* not used for input */+    info->buffers_expanded = FALSE; /* not used for input */+    info->next_buffer = 0; /* not used for input */+    info->buffer_signal = 0; /* not used for input */+    info->last_time = 0;+    info->first_message = TRUE; /* not used for input */+    info->sysex_mode = FALSE;+    info->sysex_word = 0;+    info->sysex_byte_count = 0;+    info->hdr = NULL; /* not used for input */+    info->sync_time = 0;+    info->delta = 0;+    return info;+}+++static void report_hosterror(LPWCH error_msg)+{+    WideCharToMultiByte(CP_UTF8, 0, error_msg, -1, pm_hosterror_text,+                        sizeof(pm_hosterror_text), NULL, NULL);+    if (pm_hosterror == MMSYSERR_NOMEM) {+        /* add explanation to Window's confusing error message */+        /* if there's room: */+        if (PM_HOST_ERROR_MSG_LEN - strlen(pm_hosterror_text) > 60) {+            strcat_s(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN,+                     " Probably this MIDI device is open "+                     "in another application.");+        }+    }+    pm_hosterror = TRUE;+}+++static void report_hosterror_in(void)+{+    WCHAR error_msg[PM_HOST_ERROR_MSG_LEN];+    int err = midiInGetErrorText(pm_hosterror, error_msg,+                                 PM_HOST_ERROR_MSG_LEN);+    assert(err == MMSYSERR_NOERROR);+    report_hosterror(error_msg);+}+++static void report_hosterror_out(void)+{+    WCHAR error_msg[PM_HOST_ERROR_MSG_LEN];+    int err = midiOutGetErrorText(pm_hosterror, error_msg,+                                  PM_HOST_ERROR_MSG_LEN);+    assert(err == MMSYSERR_NOERROR);+    report_hosterror(error_msg);+}+++static PmError winmm_in_open(PmInternal *midi, void *driverInfo)+{+    DWORD dwDevice;+    int i = midi->device_id;+    int max_sysex_len = midi->buffer_len * 4;+    int num_input_buffers = max_sysex_len / INPUT_SYSEX_LEN;+    winmm_info_type info;++    dwDevice = (DWORD) (intptr_t) pm_descriptors[i].descriptor;++    /* create system dependent device data */+    info = winmm_info_create();+    midi->api_info = info;+    if (!info) goto no_memory;+    /* 4000 is based on Windows documentation -- that's the value used+       in the memory manager. It's small enough that it should not+       hurt performance even if it's not optimal.+     */+    InitializeCriticalSectionAndSpinCount(&info->lock, 4000);+    /* open device */+    pm_hosterror = midiInOpen(+	                &(info->handle.in),  /* input device handle */+	                dwDevice,  /* device ID */+	                (DWORD_PTR) winmm_in_callback,  /* callback address */+	                (DWORD_PTR) midi,  /* callback instance data */+	                CALLBACK_FUNCTION); /* callback is a procedure */+    if (pm_hosterror) goto free_descriptor;++    if (num_input_buffers < MIN_INPUT_BUFFERS)+        num_input_buffers = MIN_INPUT_BUFFERS;+    for (i = 0; i < num_input_buffers; i++) {+        if (allocate_input_buffer(info->handle.in, INPUT_SYSEX_LEN)) {+            /* either pm_hosterror was set, or the proper return code+               is pmInsufficientMemory */+            goto close_device;+        }+    }+    /* start device */+    pm_hosterror = midiInStart(info->handle.in);+    if (!pm_hosterror) {+        return pmNoError;+    }++    /* undo steps leading up to the detected error */++    /* ignore return code (we already have an error to report) */+    midiInReset(info->handle.in);+close_device:+    midiInClose(info->handle.in); /* ignore return code */+free_descriptor:+    midi->api_info = NULL;+    pm_free(info);+no_memory:+    if (pm_hosterror) {+        report_hosterror_in();+        return pmHostError;+    }+    /* if !pm_hosterror, then the error must be pmInsufficientMemory */+    return pmInsufficientMemory;+    /* note: if we return an error code, the device will be+       closed and memory will be freed. It's up to the caller+       to free the parameter midi */+}+++/* winmm_in_close -- close an open midi input device */+/*+ * assume midi is non-null (checked by caller)+ */+static PmError winmm_in_close(PmInternal *midi)+{+    winmm_info_type info = (winmm_info_type) midi->api_info;+    if (!info) return pmBadPtr;+    /* device to close */+    if ((pm_hosterror = midiInStop(info->handle.in))) {+        midiInReset(info->handle.in); /* try to reset and close port */+        midiInClose(info->handle.in);+    } else if ((pm_hosterror = midiInReset(info->handle.in))) {+        midiInClose(info->handle.in); /* best effort to close midi port */+    } else {+        pm_hosterror = midiInClose(info->handle.in);+    }+    midi->api_info = NULL;+    DeleteCriticalSection(&info->lock);+    pm_free(info); /* delete */+    if (pm_hosterror) {+        report_hosterror_in();+        return pmHostError;+    }+    return pmNoError;+}+++/* Callback function executed via midiInput SW interrupt (via midiInOpen). */+static void FAR PASCAL winmm_in_callback(+    HMIDIIN hMidiIn,       /* midiInput device Handle */+    UINT wMsg,             /* midi msg */+    DWORD_PTR dwInstance,  /* application data */+    DWORD_PTR dwParam1,    /* MIDI data */+    DWORD_PTR dwParam2)    /* device timestamp (wrt most recent midiInStart) */+{+    PmInternal *midi = (PmInternal *) dwInstance;+    winmm_info_type info = (winmm_info_type) midi->api_info;++    /* NOTE: we do not just EnterCriticalSection() here because an+     * MIM_CLOSE message arrives when the port is closed, but then+     * the info->lock has been destroyed.+     */++    switch (wMsg) {+    case MIM_DATA: {+        /* if this callback is reentered with data, we're in trouble. +         * It's hard to imagine that Microsoft would allow callbacks +         * to be reentrant -- isn't the model that this is like a +         * hardware interrupt? -- but I've seen reentrant behavior +         * using a debugger, so it happens.+         */+        EnterCriticalSection(&info->lock);++        /* dwParam1 is MIDI data received, packed into DWORD w/ 1st byte of+                message LOB;+           dwParam2 is time message received by input device driver, specified+            in [ms] from when midiInStart called.+           each message is expanded to include the status byte */++        if ((dwParam1 & 0x80) == 0) {+            /* not a status byte -- ignore it. This happened running the+               sysex.c test under Win2K with MidiMan USB 1x1 interface,+               but I can't reproduce it. -RBD+             */+            /* printf("non-status byte found\n"); */+        } else { /* data to process */+            PmEvent event;+            if (midi->time_proc)+                dwParam2 = (*midi->time_proc)(midi->time_info);+            event.timestamp = (PmTimestamp)dwParam2;+            event.message = (PmMessage)dwParam1;+            pm_read_short(midi, &event);+        }+        LeaveCriticalSection(&info->lock);+        break;+    }+    case MIM_LONGDATA: {+        MIDIHDR *lpMidiHdr = (MIDIHDR *) dwParam1;+        unsigned char *data = (unsigned char *) lpMidiHdr->lpData;+        unsigned int processed = 0;+        int remaining = lpMidiHdr->dwBytesRecorded;++        EnterCriticalSection(&info->lock);+        /* printf("midi_in_callback -- lpMidiHdr %x, %d bytes, %2x...\n", +                lpMidiHdr, lpMidiHdr->dwBytesRecorded, *data); */+        if (midi->time_proc)+            dwParam2 = (*midi->time_proc)(midi->time_info);+        /* can there be more than one message in one buffer? */+        /* assume yes and iterate through them */+        pm_read_bytes(midi, data + processed, remaining, (PmTimestamp)dwParam2);++        /* when a device is closed, the pending MIM_LONGDATA buffers are+           returned to this callback with dwBytesRecorded == 0. In this+           case, we do not want to send them back to the interface (if+           we do, the interface will not close, and Windows OS may hang). */+        if (lpMidiHdr->dwBytesRecorded > 0) {+            MMRESULT rslt;+            lpMidiHdr->dwBytesRecorded = 0;+            lpMidiHdr->dwFlags = 0;+			+            /* note: no error checking -- can this actually fail? */+            rslt = midiInPrepareHeader(hMidiIn, lpMidiHdr, sizeof(MIDIHDR));+            assert(rslt == MMSYSERR_NOERROR);+            /* note: I don't think this can fail except possibly for+             * MMSYSERR_NOMEM, but the pain of reporting this+             * unlikely but probably catastrophic error does not seem+             * worth it.+             */+            rslt = midiInAddBuffer(hMidiIn, lpMidiHdr, sizeof(MIDIHDR));+            assert(rslt == MMSYSERR_NOERROR);+            LeaveCriticalSection(&info->lock);+        } else {+            midiInUnprepareHeader(hMidiIn,lpMidiHdr,sizeof(MIDIHDR));+            LeaveCriticalSection(&info->lock);+            pm_free(lpMidiHdr);+        }+        break;+    }+    case MIM_OPEN:+        break;+    case MIM_CLOSE:+        break;+    case MIM_ERROR:+        /* printf("MIM_ERROR\n"); */+        break;+    case MIM_LONGERROR:+        /* printf("MIM_LONGERROR\n"); */+        break;+    default:+        break;+    }+}++/*+===========================================================================+begin midi output implementation+===========================================================================+*/++/* begin helper routines used by midiOutStream interface */++/* add_to_buffer -- adds timestamped short msg to buffer, returns fullp */+static int add_to_buffer(winmm_info_type m, LPMIDIHDR hdr,+                         unsigned long delta, unsigned long msg)+{+    unsigned long *ptr = (unsigned long *)+                         (hdr->lpData + hdr->dwBytesRecorded);+    *ptr++ = delta; /* dwDeltaTime */+    *ptr++ = 0;     /* dwStream */+    *ptr++ = msg;   /* dwEvent */+    hdr->dwBytesRecorded += 3 * sizeof(long);+    /* if the addition of three more words (a message) would extend beyond+       the buffer length, then return TRUE (full)+     */+    return hdr->dwBytesRecorded + 3 * sizeof(long) > hdr->dwBufferLength;+}+++static PmTimestamp pm_time_get(winmm_info_type info)+{+    MMTIME mmtime;+    MMRESULT wRtn;+    mmtime.wType = TIME_TICKS;+    mmtime.u.ticks = 0;+    wRtn = midiStreamPosition(info->handle.stream, &mmtime, sizeof(mmtime));+    assert(wRtn == MMSYSERR_NOERROR);+    return mmtime.u.ticks;+}+++/* end helper routines used by midiOutStream interface */+++static PmError winmm_out_open(PmInternal *midi, void *driverInfo)+{+    DWORD dwDevice;+    int i = midi->device_id;+    winmm_info_type info;+    MIDIPROPTEMPO propdata;+    MIDIPROPTIMEDIV divdata;+    int max_sysex_len = midi->buffer_len * 4;+    int output_buffer_len;+    int num_buffers;+    dwDevice = (DWORD) (intptr_t) pm_descriptors[i].descriptor;++    /* create system dependent device data */+    info = winmm_info_create();+    midi->api_info = info;+    if (!info) goto no_memory;+    /* create a signal */+    info->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL);+    /* this should only fail when there are very serious problems */+    assert(info->buffer_signal);+    /* open device */+    if (midi->latency == 0) {+        /* use simple midi out calls */+        pm_hosterror = midiOutOpen(+                (LPHMIDIOUT) & info->handle.out,  /* device Handle */+		        dwDevice,  /* device ID  */+		        /* note: same callback fn as for StreamOpen: */+		        (DWORD_PTR) winmm_streamout_callback, /* callback fn */+		        (DWORD_PTR) midi,  /* callback instance data */+		        CALLBACK_FUNCTION); /* callback type */+    } else {+        /* use stream-based midi output (schedulable in future) */+        pm_hosterror = midiStreamOpen(+	            &info->handle.stream,  /* device Handle */+		        (LPUINT) & dwDevice,  /* device ID pointer */+		        1,  /* reserved, must be 1 */+		        (DWORD_PTR) winmm_streamout_callback,+		        (DWORD_PTR) midi,  /* callback instance data */+		        CALLBACK_FUNCTION);+    }+    if (pm_hosterror != MMSYSERR_NOERROR) {+        goto free_descriptor;+    }++    if (midi->latency == 0) {+        num_buffers = NUM_SIMPLE_SYSEX_BUFFERS;+        output_buffer_len = max_sysex_len / num_buffers;+        if (output_buffer_len < MIN_SIMPLE_SYSEX_LEN)+            output_buffer_len = MIN_SIMPLE_SYSEX_LEN;+    } else {+        num_buffers = max(midi->buffer_len, midi->latency / 2);+        if (num_buffers < MIN_STREAM_BUFFERS)+            num_buffers = MIN_STREAM_BUFFERS;+        output_buffer_len = STREAM_BUFFER_LEN;++        propdata.cbStruct = sizeof(MIDIPROPTEMPO);+        propdata.dwTempo = 480000; /* microseconds per quarter */+        pm_hosterror = midiStreamProperty(info->handle.stream,+                                          (LPBYTE) & propdata,+                                          MIDIPROP_SET | MIDIPROP_TEMPO);+        if (pm_hosterror) goto close_device;++        divdata.cbStruct = sizeof(MIDIPROPTEMPO);+        divdata.dwTimeDiv = 480;   /* divisions per quarter */+        pm_hosterror = midiStreamProperty(info->handle.stream,+                                          (LPBYTE) & divdata,+                                          MIDIPROP_SET | MIDIPROP_TIMEDIV);+        if (pm_hosterror) goto close_device;+    }+    /* allocate buffers */+    if (allocate_buffers(info, output_buffer_len, num_buffers)) +        goto free_buffers;+    /* start device */+    if (midi->latency != 0) {+        pm_hosterror = midiStreamRestart(info->handle.stream);+        if (pm_hosterror != MMSYSERR_NOERROR) goto free_buffers;+    }+    return pmNoError;++free_buffers:+    /* buffers are freed below by winmm_out_delete */+close_device:+    midiOutClose(info->handle.out);+free_descriptor:+    midi->api_info = NULL;+    winmm_out_delete(midi); /* frees buffers and m */+no_memory:+    if (pm_hosterror) {+        report_hosterror_out();+        return pmHostError;+    }+    return pmInsufficientMemory;+}+++/* winmm_out_delete -- carefully free data associated with midi */+/**/+static void winmm_out_delete(PmInternal *midi)+{+    int i;+    /* delete system dependent device data */+    winmm_info_type info = (winmm_info_type) midi->api_info;+    if (info) {+        if (info->buffer_signal) {+            /* don't report errors -- better not to stop cleanup */+            CloseHandle(info->buffer_signal);+        }+        /* if using stream output, free buffers */+        for (i = 0; i < info->num_buffers; i++) {+            if (info->buffers[i]) pm_free(info->buffers[i]);+        }+        info->num_buffers = 0;+        pm_free(info->buffers);+        info->max_buffers = 0;+    }+    midi->api_info = NULL;+    pm_free(info); /* delete */+}+++/* see comments for winmm_in_close */+static PmError winmm_out_close(PmInternal *midi)+{+    winmm_info_type info = (winmm_info_type) midi->api_info;+    if (info->handle.out) {+        /* device to close */+        if (midi->latency == 0) {+            pm_hosterror = midiOutClose(info->handle.out);+        } else {+            pm_hosterror = midiStreamClose(info->handle.stream);+        }+        /* regardless of outcome, free memory */+        winmm_out_delete(midi);+    }+    if (pm_hosterror) {+        report_hosterror_out();+        return pmHostError;+    }+    return pmNoError;+}+++static PmError winmm_out_abort(PmInternal *midi)+{+    winmm_info_type info = (winmm_info_type) midi->api_info;++    /* only stop output streams */+    if (midi->latency > 0) {+        pm_hosterror = midiStreamStop(info->handle.stream);+        if (pm_hosterror) {+            report_hosterror_out();+            return pmHostError;+        }+    }+    return pmNoError;+}+++static PmError winmm_write_flush(PmInternal *midi, PmTimestamp timestamp)+{+    winmm_info_type info = (winmm_info_type) midi->api_info;+    assert(info);+    if (info->hdr) {+        pm_hosterror = midiOutPrepareHeader(info->handle.out, info->hdr, +                                            sizeof(MIDIHDR));+        if (pm_hosterror) {+            /* do not send message */+        } else if (midi->latency == 0) {+            /* As pointed out by Nigel Brown, 20Sep06, dwBytesRecorded+             * should be zero. This is set in get_free_sysex_buffer(). +             * The msg length goes in dwBufferLength in spite of what+             * Microsoft documentation says (or doesn't say). */+            info->hdr->dwBufferLength = info->hdr->dwBytesRecorded;+            info->hdr->dwBytesRecorded = 0;+            pm_hosterror = midiOutLongMsg(info->handle.out, info->hdr, +                                          sizeof(MIDIHDR));+        } else {+            pm_hosterror = midiStreamOut(info->handle.stream, info->hdr, +                                         sizeof(MIDIHDR));+        }+        midi->fill_base = NULL;+        info->hdr = NULL;+        if (pm_hosterror) {+            report_hosterror_out();+            return pmHostError;+        }+    }+    return pmNoError;+}+++static PmError winmm_write_short(PmInternal *midi, PmEvent *event)+{+    winmm_info_type info = (winmm_info_type) midi->api_info;+    PmError rslt = pmNoError;+    assert(info);++    if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */+        pm_hosterror = midiOutShortMsg(info->handle.out, event->message);+        if (pm_hosterror) {+            if (info->hdr) {  /* device disconnect may delete hdr */+                info->hdr->dwFlags = 0; /* release the buffer */+            }+            report_hosterror_out();+            return pmHostError;+        }+    } else {  /* use midiStream interface -- pass data through buffers */+        unsigned long when = event->timestamp;+        unsigned long delta;+        int full;+        if (when == 0) when = midi->now;+        /* when is in real_time; translate to intended stream time */+        when = when + info->delta + midi->latency;+        /* make sure we don't go backward in time */+        if (when < info->last_time) when = info->last_time;+        delta = when - info->last_time;+        info->last_time = when;+        /* before we insert any data, we must have a buffer */+        if (info->hdr == NULL) {+            /* stream interface: buffers allocated when stream is opened */+            info->hdr = get_free_output_buffer(midi);+        }+        full = add_to_buffer(info, info->hdr, delta, event->message);+        /* note: winmm_write_flush sets pm_hosterror etc. on host error */+        if (full) rslt = winmm_write_flush(midi, when);+    }+    return rslt;+}++#define winmm_begin_sysex winmm_write_flush+#ifndef winmm_begin_sysex+static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp)+{+    winmm_info_type m = (winmm_info_type) midi->api_info;+    PmError rslt = pmNoError;++    if (midi->latency == 0) {+        /* do nothing -- it's handled in winmm_write_byte */+    } else {+        /* sysex expects an empty sysex buffer, so send whatever is here */+        rslt = winmm_write_flush(midi);+    }+    return rslt;+}+#endif++static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp)+{+    /* could check for callback_error here, but I haven't checked+     * what happens if we exit early and don't finish the sysex msg+     * and clean up+     */+    winmm_info_type info = (winmm_info_type) midi->api_info;+    PmError rslt = pmNoError;+    LPMIDIHDR hdr = info->hdr;+    if (!hdr) return rslt; /* something bad happened earlier,+            do not report an error because it would have been +            reported (at least) once already */+    /* a(n old) version of MIDI YOKE requires a zero byte after+     * the sysex message, but do not increment dwBytesRecorded: */+    hdr->lpData[hdr->dwBytesRecorded] = 0;+    if (midi->latency == 0) {+#ifdef DEBUG_PRINT_BEFORE_SENDING_SYSEX+        /* DEBUG CODE: */+        { int i; int len = info->hdr->dwBufferLength;+          printf("OutLongMsg %d ", len);+          for (i = 0; i < len; i++) {+              printf("%2x ", (unsigned char) (info->hdr->lpData[i]));+          }+        }+#endif+    } else {+        /* Using stream interface. There are accumulated bytes in info->hdr+           to send using midiStreamOut+         */+        /* add bytes recorded to MIDIEVENT length, but don't+           count the MIDIEVENT data (3 longs) */+        MIDIEVENT *evt = (MIDIEVENT *) (hdr->lpData);+        evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long);+        /* round up BytesRecorded to multiple of 4 */+        hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3;+    }+    rslt = winmm_write_flush(midi, timestamp);+    return rslt;+}+++static PmError winmm_write_byte(PmInternal *midi, unsigned char byte,+                                PmTimestamp timestamp)+{+    /* write a sysex byte */+    PmError rslt = pmNoError;+    winmm_info_type info = (winmm_info_type) midi->api_info;+    LPMIDIHDR hdr = info->hdr;+    unsigned char *msg_buffer;+    assert(info);+    if (!hdr) {+        info->hdr = hdr = get_free_output_buffer(midi);+        assert(hdr);+        midi->fill_base = (unsigned char *) info->hdr->lpData;+        midi->fill_offset_ptr = (uint32_t *) &(hdr->dwBytesRecorded);+        /* when buffer fills, Pm_WriteSysEx will revert to calling+         * pmwin_write_byte, which expect to have space, so leave+         * one byte free for pmwin_write_byte. Leave another byte+         * of space for zero after message to make early version of +         * MIDI YOKE driver happy -- therefore dwBufferLength - 2 */+        midi->fill_length = hdr->dwBufferLength - 2;+        if (midi->latency != 0) {+            unsigned long when = (unsigned long) timestamp;+            unsigned long delta;+            unsigned long *ptr;+            if (when == 0) when = midi->now;+            /* when is in real_time; translate to intended stream time */+            when = when + info->delta + midi->latency;+            /* make sure we don't go backward in time */+            if (when < info->last_time) when = info->last_time;+            delta = when - info->last_time;+            info->last_time = when;++            ptr = (unsigned long *) hdr->lpData;+            *ptr++ = delta;+            *ptr++ = 0;+            *ptr = MEVT_F_LONG;+            hdr->dwBytesRecorded = 3 * sizeof(long);+            /* data will be added at an offset of dwBytesRecorded ... */+        }+    }+    /* add the data byte */+    msg_buffer = (unsigned char *) (hdr->lpData);+    msg_buffer[hdr->dwBytesRecorded++] = byte;++    /* see if buffer is full, leave one byte extra for pad */+    if (hdr->dwBytesRecorded >= hdr->dwBufferLength - 1) {+        /* write what we've got and continue */+        rslt = winmm_end_sysex(midi, timestamp); +    }+    return rslt;+}+++static PmTimestamp winmm_synchronize(PmInternal *midi)+{+    winmm_info_type info;+    unsigned long pm_stream_time_2;+    unsigned long real_time;+    unsigned long pm_stream_time;++    /* only synchronize if we are using stream interface */+    if (midi->latency == 0) return 0;++    /* figure out the time */+    info = (winmm_info_type) midi->api_info;+    pm_stream_time_2 = pm_time_get(info);++    do {+        /* read real_time between two reads of stream time */+        pm_stream_time = pm_stream_time_2;+        real_time = (*midi->time_proc)(midi->time_info);+        pm_stream_time_2 = pm_time_get(info);+        /* repeat if more than 1ms elapsed */+    } while (pm_stream_time_2 > pm_stream_time + 1);+    info->delta = pm_stream_time - real_time;+    info->sync_time = real_time;+    return real_time;+}+++/* winmm_streamout_callback -- unprepare (free) buffer header */+static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,+        DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)+{+    PmInternal *midi = (PmInternal *) dwInstance;+    winmm_info_type info = (winmm_info_type) midi->api_info;+    LPMIDIHDR hdr = (LPMIDIHDR) dwParam1;+    int err;++    /* Even if an error is pending, I think we should unprepare msgs and+       signal their arrival+     */+    /* printf("streamout_callback: hdr %x, wMsg %x, MOM_DONE %x\n", +           hdr, wMsg, MOM_DONE); */+    if (wMsg == MOM_DONE) {+        MMRESULT ret = midiOutUnprepareHeader(info->handle.out, hdr, +                                              sizeof(MIDIHDR));+        assert(ret == MMSYSERR_NOERROR);+    } else if (wMsg == MOM_CLOSE) {+        /* The streaming API gets a callback when the device is closed.+         * The non-streaming API gets a callback when the device is+         * removed or closed. It is misleading to set is_removed when+         * the device is closed normally, but in that case, midi itself+         * will be freed immediately, so there should be no way to+         * observe is_removed == TRUE. On the other hand, if the device+         * is removed, setting is_removed will cause PortMidi to return+         * the pmDeviceRemoved error on attempts to output to the device.+         *     In the case of normal closing, due to midiOutClose(), +         * the call below is reentrant (!), but for some reason this does+         * not cause an error or infinite recursion, so we are not taking+         * any precautions to flag midi as "in the process of closing."+         */+        midi->is_removed = TRUE;+        midiOutClose(info->handle.out);+    }+    /* signal client in case it is blocked waiting for buffer */+    err = SetEvent(info->buffer_signal);+    assert(err); /* false -> error */+}+++/*+===========================================================================+begin exported functions+===========================================================================+*/++#define winmm_in_abort pm_fail_fn+pm_fns_node pm_winmm_in_dictionary = {+                                         none_write_short,+                                         none_sysex,+                                         none_sysex,+                                         none_write_byte,+                                         none_write_short,+                                         none_write_flush,+                                         winmm_synchronize,+                                         winmm_in_open,+                                         winmm_in_abort,+                                         winmm_in_close,+                                         success_poll,+                                         winmm_check_host_error+                                     };++pm_fns_node pm_winmm_out_dictionary = {+                                          winmm_write_short,+                                          winmm_begin_sysex,+                                          winmm_end_sysex,+                                          winmm_write_byte,+            /* short realtime message: */ winmm_write_short,  +                                          winmm_write_flush,+                                          winmm_synchronize,+                                          winmm_out_open,+                                          winmm_out_abort,+                                          winmm_out_close,+                                          none_poll,+                                          winmm_check_host_error+                                      };+++/* initialize winmm interface. Note that if there is something wrong+   with winmm (e.g. it is not supported or installed), it is not an+   error. We should simply return without having added any devices to+   the table. Hence, no error code is returned. Furthermore, this init+   code is called along with every other supported interface, so the+   user would have a very hard time figuring out what hardware and API+   generated the error. Finally, it would add complexity to pmwin.c to+   remember where the error code came from in order to convert to text.+ */+void pm_winmm_init( void )+{+    pm_winmm_mapper_input();+    pm_winmm_mapper_output();+    pm_winmm_general_inputs();+    pm_winmm_general_outputs();+}+++/* no error codes are returned, even if errors are encountered, because+   there is probably nothing the user could do (e.g. it would be an error+   to retry.+ */+void pm_winmm_term( void )+{+    int i;+#ifdef MMDEBUG+    int doneAny = 0;+    printf("pm_winmm_term called\n");+#endif+    for (i = 0; i < pm_descriptor_len; i++) {+        PmInternal *midi = pm_descriptors[i].pm_internal;+        if (midi) {+            winmm_info_type info = (winmm_info_type) midi->api_info;+            if (info->handle.out) {+                /* close next open device*/+#ifdef MMDEBUG+                if (doneAny == 0) {+                    printf("begin closing open devices...\n");+                    doneAny = 1;+                }+#endif+                /* close all open ports */+                (*midi->dictionary->close)(midi);+            }+        }+    }+    if (midi_in_caps) {+        pm_free(midi_in_caps);+        midi_in_caps = NULL;+    }+    if (midi_out_caps) {+        pm_free(midi_out_caps);+        midi_out_caps = NULL;+    }+#ifdef MMDEBUG+    if (doneAny) {+        printf("warning: devices were left open. They have been closed.\n");+    }+    printf("pm_winmm_term exiting\n");+#endif+    pm_descriptor_len = 0;+}
portmidi/pm_win/pmwinmm.h view
@@ -1,5 +1,5 @@-/* midiwin32.h -- system-specific definitions */
-
-void pm_winmm_init( void );
-void pm_winmm_term( void );
-
+/* pmwinmm.h -- system-specific definitions for windows multimedia API */++void pm_winmm_init( void );+void pm_winmm_term( void );+
portmidi/porttime/porttime.h view
@@ -1,72 +1,106 @@-/* porttime.h -- portable interface to millisecond timer */
-
-/* CHANGE LOG FOR PORTTIME
-  10-Jun-03 Mark Nelson & RBD
-    boost priority of timer thread in ptlinux.c implementation
- */
-
-/* Should there be a way to choose the source of time here? */
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-
-typedef enum {
-    ptNoError = 0,         /* success */
-    ptHostError = -10000,  /* a system-specific error occurred */
-    ptAlreadyStarted,      /* cannot start timer because it is already started */
-    ptAlreadyStopped,      /* cannot stop timer because it is already stopped */
-    ptInsufficientMemory   /* memory could not be allocated */
-} PtError;
-
-
-typedef long PtTimestamp;
-
-typedef void (PtCallback)( PtTimestamp timestamp, void *userData );
-
-/*
-    Pt_Start() starts a real-time service.
-
-    resolution is the timer resolution in ms. The time will advance every
-    resolution ms.
-
-    callback is a function pointer to be called every resolution ms.
-
-    userData is passed to callback as a parameter.
-
-    return value:
-    Upon success, returns ptNoError. See PtError for other values.
-*/
-PtError Pt_Start(int resolution, PtCallback *callback, void *userData);
-
-/*
-    Pt_Stop() stops the timer.
-
-    return value:
-    Upon success, returns ptNoError. See PtError for other values.
-*/
-PtError Pt_Stop();
-
-/*
-    Pt_Started() returns true iff the timer is running.
-*/
-int Pt_Started();
-
-/* 
-    Pt_Time() returns the current time in ms.
-*/
-PtTimestamp Pt_Time();
-
-/*
-    Pt_Sleep() pauses, allowing other threads to run.
-
-    duration is the length of the pause in ms. The true duration 
-    of the pause may be rounded to the nearest or next clock tick
-    as determined by resolution in Pt_Start().
-*/
-void Pt_Sleep(long duration);
-
-#ifdef __cplusplus
-}
-#endif
+/** @file porttime.h portable interface to millisecond timer. */++/* CHANGE LOG FOR PORTTIME+  10-Jun-03 Mark Nelson & RBD+    boost priority of timer thread in ptlinux.c implementation+ */++#ifndef PORTMIDI_PORTTIME_H+#define PORTMIDI_PORTTIME_H++/* Should there be a way to choose the source of time here? */++#ifdef WIN32+#ifndef INT32_DEFINED+/* rather than having users install a special .h file for windows, +   just put the required definitions inline here. portmidi.h uses+   these too, so the definitions are (unfortunately) duplicated there+*/+typedef int int32_t;+typedef unsigned int uint32_t;+#define INT32_DEFINED+#endif+#else+#include <stdint.h> /* needed for int32_t */+#endif++#ifdef __cplusplus+extern "C" {+#endif++#ifndef PMEXPORT+#ifdef _WINDLL+#define PMEXPORT __declspec(dllexport)+#else+#define PMEXPORT +#endif+#endif++/** @defgroup grp_porttime PortTime: Millisecond Timer+    @{+*/++typedef enum {+    ptNoError = 0,         /**< success */+    ptHostError = -10000,  /**< a system-specific error occurred */+    ptAlreadyStarted,      /**< cannot start timer because it is already+                              started */+    ptAlreadyStopped,      /**< cannot stop timer because it is already+                              stopped */+    ptInsufficientMemory   /**< memory could not be allocated */+} PtError; /**< @brief @enum  PtError PortTime error code; a common return type.+            * No error is indicated by zero; errors are indicated by < 0.+            */++/** real time or time offset in milliseconds. */+typedef int32_t PtTimestamp;++/** a function that gets a current time */+typedef void (PtCallback)(PtTimestamp timestamp, void *userData);++/** start a real-time clock service.++    @param resolution the timer resolution in ms. The time will advance every+    \p resolution ms.++    @param callback a function pointer to be called every resolution ms.++    @param userData is passed to \p callback as a parameter.++    @return #ptNoError on success. See #PtError for other values.+*/+PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData);++/** stop the timer.++    @return #ptNoError on success. See #PtError for other values.+*/+PMEXPORT PtError Pt_Stop(void);++/** test if the timer is running.+ +    @return TRUE or FALSE+*/+PMEXPORT int Pt_Started(void);++/** get the current time in ms.++    @return the current time+*/+PMEXPORT PtTimestamp Pt_Time(void);++/** pauses the current thread, allowing other threads to run.++    @param duration the length of the pause in ms. The true duration +    of the pause may be rounded to the nearest or next clock tick+    as determined by resolution in #Pt_Start().+*/+PMEXPORT void Pt_Sleep(int32_t duration);++/** @} */++#ifdef __cplusplus+}+#endif++#endif /* PORTMIDI_PORTTIME_H */
portmidi/porttime/ptlinux.c view
@@ -1,133 +1,139 @@-/* ptlinux.c -- portable timer implementation for linux */
-
-
-/* IMPLEMENTATION NOTES (by Mark Nelson): 
-
-Unlike Windows, Linux has no system call to request a periodic callback,
-so if Pt_Start() receives a callback parameter, it must create a thread
-that wakes up periodically and calls the provided callback function.
-If running as superuser, use setpriority() to renice thread to -20.  
-One could also set the timer thread to a real-time priority (SCHED_FIFO
-and SCHED_RR), but this is dangerous for This is necessary because  
-if the callback hangs it'll never return. A more serious reason
-is that the current scheduler implementation busy-waits instead 
-of sleeping when realtime threads request a sleep of <=2ms (as a way 
-to get around the 10ms granularity), which means the thread would never 
-let anyone else on the CPU.
-
-CHANGE LOG
-
-18-Jul-03 Roger Dannenberg -- Simplified code to set priority of timer
-            thread. Simplified implementation notes. 
-
-*/
-/* stdlib, stdio, unistd, and sys/types were added because they appeared
- * in a Gentoo patch, but I'm not sure why they are needed. -RBD
- */
-#include <stdlib.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include "porttime.h"
-#include "sys/time.h"
-#include "sys/resource.h"
-#include "sys/timeb.h"
-#include "pthread.h"
-
-#define TRUE 1
-#define FALSE 0
-
-static int time_started_flag = FALSE;
-static struct timeb time_offset = {0, 0, 0, 0};
-static pthread_t pt_thread_pid;
-
-/* note that this is static data -- we only need one copy */
-typedef struct {
-    int id;
-    int resolution;
-    PtCallback *callback;
-    void *userData;
-} pt_callback_parameters;
-
-static int pt_callback_proc_id = 0;
-
-static void *Pt_CallbackProc(void *p)
-{
-    pt_callback_parameters *parameters = (pt_callback_parameters *) p;
-    int mytime = 1;
-    /* to kill a process, just increment the pt_callback_proc_id */
-    /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id,
-           parameters->id); */
-    if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20);
-	while (pt_callback_proc_id == parameters->id) {
-        /* wait for a multiple of resolution ms */
-        struct timeval timeout;
-        int delay = mytime++ * parameters->resolution - Pt_Time();
-        if (delay < 0) delay = 0;
-        timeout.tv_sec = 0;
-        timeout.tv_usec = delay * 1000;
-        select(0, NULL, NULL, NULL, &timeout);
-        (*(parameters->callback))(Pt_Time(), parameters->userData);
-    }
-    /* printf("Pt_CallbackProc exiting\n"); */
-    // free(parameters);
-    return NULL;
-}
-
-
-PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
-{
-    if (time_started_flag) return ptNoError;
-    ftime(&time_offset); /* need this set before process runs */
-    if (callback) {
-        int res;
-        pt_callback_parameters *parms = (pt_callback_parameters *) 
-            malloc(sizeof(pt_callback_parameters));
-        if (!parms) return ptInsufficientMemory;
-        parms->id = pt_callback_proc_id;
-        parms->resolution = resolution;
-        parms->callback = callback;
-        parms->userData = userData;
-        res = pthread_create(&pt_thread_pid, NULL, 
-                             Pt_CallbackProc, parms);
-        if (res != 0) return ptHostError;
-    }
-    time_started_flag = TRUE;
-    return ptNoError;
-}
-
-
-PtError Pt_Stop()
-{
-    /* printf("Pt_Stop called\n"); */
-    pt_callback_proc_id++;
-    pthread_join(pt_thread_pid, NULL);
-    time_started_flag = FALSE;
-    return ptNoError;
-}
-
-
-int Pt_Started()
-{
-    return time_started_flag;
-}
-
-
-PtTimestamp Pt_Time()
-{
-    long seconds, milliseconds;
-    struct timeb now;
-    ftime(&now);
-    seconds = now.time - time_offset.time;
-    milliseconds = now.millitm - time_offset.millitm;
-    return seconds * 1000 + milliseconds;
-}
-
-
-void Pt_Sleep(long duration)
-{
-    usleep(duration * 1000);
-}
-
-
-
+/* ptlinux.c -- portable timer implementation for linux */+++/* IMPLEMENTATION NOTES (by Mark Nelson): ++Unlike Windows, Linux has no system call to request a periodic callback,+so if Pt_Start() receives a callback parameter, it must create a thread+that wakes up periodically and calls the provided callback function.+If running as superuser, use setpriority() to renice thread to -20.  +One could also set the timer thread to a real-time priority (SCHED_FIFO+and SCHED_RR), but this is dangerous for This is necessary because  +if the callback hangs it'll never return. A more serious reason+is that the current scheduler implementation busy-waits instead +of sleeping when realtime threads request a sleep of <=2ms (as a way +to get around the 10ms granularity), which means the thread would never +let anyone else on the CPU.++CHANGE LOG++18-Jul-03 Roger Dannenberg -- Simplified code to set priority of timer+            thread. Simplified implementation notes. ++*/+/* stdlib, stdio, unistd, and sys/types were added because they appeared+ * in a Gentoo patch, but I'm not sure why they are needed. -RBD+ */+#include <stdlib.h>+#include <stdio.h>+#include <unistd.h>+#include <sys/types.h>+#include "porttime.h"+#include "time.h"+#include "sys/resource.h"+#include "pthread.h"++#define TRUE 1+#define FALSE 0++#ifndef CLOCK_MONOTONIC_RAW+#define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC+#endif++static int time_started_flag = FALSE;+static struct timespec time_offset = {0, 0};+static pthread_t pt_thread_pid;+static int pt_thread_created = FALSE;++/* note that this is static data -- we only need one copy */+typedef struct {+    int id;+    int resolution;+    PtCallback *callback;+    void *userData;+} pt_callback_parameters;++static int pt_callback_proc_id = 0;++static void *Pt_CallbackProc(void *p)+{+    pt_callback_parameters *parameters = (pt_callback_parameters *) p;+    int mytime = 1;+    /* to kill a process, just increment the pt_callback_proc_id */+    /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id,+           parameters->id); */+    if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20);+    while (pt_callback_proc_id == parameters->id) {+        /* wait for a multiple of resolution ms */+        struct timeval timeout;+        int delay = mytime++ * parameters->resolution - Pt_Time();+        if (delay < 0) delay = 0;+        timeout.tv_sec = 0;+        timeout.tv_usec = delay * 1000;+        select(0, NULL, NULL, NULL, &timeout);+        (*(parameters->callback))(Pt_Time(), parameters->userData);+    }+    /* printf("Pt_CallbackProc exiting\n"); */+    // free(parameters);+    return NULL;+}+++PtError Pt_Start(int resolution, PtCallback *callback, void *userData)+{+    if (time_started_flag) return ptNoError;+    /* need this set before process runs: */+    clock_gettime(CLOCK_MONOTONIC_RAW, &time_offset);+    if (callback) {+        int res;+        pt_callback_parameters *parms = (pt_callback_parameters *)+            malloc(sizeof(pt_callback_parameters));+        if (!parms) return ptInsufficientMemory;+        parms->id = pt_callback_proc_id;+        parms->resolution = resolution;+        parms->callback = callback;+        parms->userData = userData;+        res = pthread_create(&pt_thread_pid, NULL,+                             Pt_CallbackProc, parms);+        if (res != 0) return ptHostError;+        pt_thread_created = TRUE;+    }+    time_started_flag = TRUE;+    return ptNoError;+}+++PtError Pt_Stop(void)+{+    /* printf("Pt_Stop called\n"); */+    pt_callback_proc_id++;+    if (pt_thread_created) {+        pthread_join(pt_thread_pid, NULL);+        pt_thread_created = FALSE;+    }+    time_started_flag = FALSE;+    return ptNoError;+}+++int Pt_Started(void)+{+    return time_started_flag;+}+++PtTimestamp Pt_Time(void)+{+    long seconds, ms;+    struct timespec now;+    clock_gettime(CLOCK_MONOTONIC_RAW, &now);+    seconds = now.tv_sec - time_offset.tv_sec;+    ms = (now.tv_nsec - time_offset.tv_nsec) / 1000000; /* round down */+    return seconds * 1000 + ms;+}+++void Pt_Sleep(int32_t duration)+{+    usleep(duration * 1000);+}
+ portmidi/porttime/ptmacosx_cf.c view
@@ -0,0 +1,140 @@+/* ptmacosx.c -- portable timer implementation for mac os x */++#include <stdlib.h>+#include <stdio.h>+#include <pthread.h>+#include <CoreFoundation/CoreFoundation.h>++#import <mach/mach.h>+#import <mach/mach_error.h>+#import <mach/mach_time.h>+#import <mach/clock.h>++#include "porttime.h"++#define THREAD_IMPORTANCE 30+#define LONG_TIME 1000000000.0++static int time_started_flag = FALSE;+static CFAbsoluteTime startTime = 0.0;+static CFRunLoopRef timerRunLoop;++typedef struct {+    int resolution;+    PtCallback *callback;+    void *userData;+} PtThreadParams;+++void Pt_CFTimerCallback(CFRunLoopTimerRef timer, void *info)+{+    PtThreadParams *params = (PtThreadParams*)info;+    (*params->callback)(Pt_Time(), params->userData);+}++static void* Pt_Thread(void *p)+{+    CFTimeInterval timerInterval;+    CFRunLoopTimerContext timerContext;+    CFRunLoopTimerRef timer;+    PtThreadParams *params = (PtThreadParams*)p;+    /* CFTimeInterval timeout; */++    /* raise the thread's priority */+    kern_return_t error;+    thread_extended_policy_data_t extendedPolicy;+    thread_precedence_policy_data_t precedencePolicy;++    extendedPolicy.timeshare = 0;+    error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY,+                              (thread_policy_t)&extendedPolicy,+                              THREAD_EXTENDED_POLICY_COUNT);+    if (error != KERN_SUCCESS) {+        mach_error("Couldn't set thread timeshare policy", error);+    }++    precedencePolicy.importance = THREAD_IMPORTANCE;+    error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY,+                              (thread_policy_t)&precedencePolicy,+                              THREAD_PRECEDENCE_POLICY_COUNT);+    if (error != KERN_SUCCESS) {+        mach_error("Couldn't set thread precedence policy", error);+    }++    /* set up the timer context */+    timerContext.version = 0;+    timerContext.info = params;+    timerContext.retain = NULL;+    timerContext.release = NULL;+    timerContext.copyDescription = NULL;++    /* create a new timer */+    timerInterval = (double)params->resolution / 1000.0;+    timer = CFRunLoopTimerCreate(NULL, startTime+timerInterval, timerInterval,+                                 0, 0, Pt_CFTimerCallback, &timerContext);++    timerRunLoop = CFRunLoopGetCurrent();+    CFRunLoopAddTimer(timerRunLoop, timer, CFSTR("PtTimeMode"));++    /* run until we're told to stop by Pt_Stop() */+    CFRunLoopRunInMode(CFSTR("PtTimeMode"), LONG_TIME, false);+    +    CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, CFSTR("PtTimeMode"));+    CFRelease(timer);+    free(params);++    return NULL;+}++PtError Pt_Start(int resolution, PtCallback *callback, void *userData)+{+    PtThreadParams *params = (PtThreadParams*)malloc(sizeof(PtThreadParams));+    pthread_t pthread_id;++    printf("Pt_Start() called\n");++    /* make sure we're not already playing */+    if (time_started_flag) return ptAlreadyStarted;+    startTime = CFAbsoluteTimeGetCurrent();++    if (callback) {+    +        params->resolution = resolution;+        params->callback = callback;+        params->userData = userData;+    +        pthread_create(&pthread_id, NULL, Pt_Thread, params);+    }++    time_started_flag = TRUE;+    return ptNoError;+}+++PtError Pt_Stop(void)+{+    printf("Pt_Stop called\n");++    CFRunLoopStop(timerRunLoop);+    time_started_flag = FALSE;+    return ptNoError;+}+++int Pt_Started(void)+{+    return time_started_flag;+}+++PtTimestamp Pt_Time(void)+{+    CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();+    return (PtTimestamp) ((now - startTime) * 1000.0);+}+++void Pt_Sleep(int32_t duration)+{+    usleep(duration * 1000);+}
portmidi/porttime/ptmacosx_mach.c view
@@ -1,130 +1,205 @@-/* ptmacosx.c -- portable timer implementation for mac os x */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <CoreAudio/HostTime.h>
-
-#import <mach/mach.h>
-#import <mach/mach_error.h>
-#import <mach/mach_time.h>
-#import <mach/clock.h>
-#include <unistd.h>
-
-#include "porttime.h"
-#include "sys/time.h"
-#include "pthread.h"
-
-#define NSEC_PER_MSEC 1000000
-#define THREAD_IMPORTANCE 30
-
-static int time_started_flag = FALSE;
-static UInt64 start_time;
-static pthread_t pt_thread_pid;
-
-/* note that this is static data -- we only need one copy */
-typedef struct {
-    int id;
-    int resolution;
-    PtCallback *callback;
-    void *userData;
-} pt_callback_parameters;
-
-static int pt_callback_proc_id = 0;
-
-static void *Pt_CallbackProc(void *p)
-{
-    pt_callback_parameters *parameters = (pt_callback_parameters *) p;
-    int mytime = 1;
-
-    kern_return_t error;
-    thread_extended_policy_data_t extendedPolicy;
-    thread_precedence_policy_data_t precedencePolicy;
-
-    extendedPolicy.timeshare = 0;
-    error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY,
-                              (thread_policy_t)&extendedPolicy,
-                              THREAD_EXTENDED_POLICY_COUNT);
-    if (error != KERN_SUCCESS) {
-        mach_error("Couldn't set thread timeshare policy", error);
-    }
-
-    precedencePolicy.importance = THREAD_IMPORTANCE;
-    error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY,
-                              (thread_policy_t)&precedencePolicy,
-                              THREAD_PRECEDENCE_POLICY_COUNT);
-    if (error != KERN_SUCCESS) {
-        mach_error("Couldn't set thread precedence policy", error);
-    }
-    
-    
-    /* to kill a process, just increment the pt_callback_proc_id */
-    /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, parameters->id); */
-    while (pt_callback_proc_id == parameters->id) {
-        /* wait for a multiple of resolution ms */
-        UInt64 wait_time;
-        int delay = mytime++ * parameters->resolution - Pt_Time();
-	long timestamp;
-        if (delay < 0) delay = 0;
-        wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC);
-        wait_time += AudioGetCurrentHostTime();
-        error = mach_wait_until(wait_time);
-	timestamp = Pt_Time();
-        (*(parameters->callback))(timestamp, parameters->userData);
-    }
-    free(parameters);
-    return NULL;
-}
-
-
-PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
-{
-    if (time_started_flag) return ptAlreadyStarted;
-    start_time = AudioGetCurrentHostTime();
-    
-    if (callback) {
-        int res;
-        pt_callback_parameters *parms;
-
-        parms = (pt_callback_parameters *) malloc(sizeof(pt_callback_parameters));
-        if (!parms) return ptInsufficientMemory;
-        parms->id = pt_callback_proc_id;
-        parms->resolution = resolution;
-        parms->callback = callback;
-        parms->userData = userData;
-        res = pthread_create(&pt_thread_pid, NULL, Pt_CallbackProc, parms);
-        if (res != 0) return ptHostError;
-    }
-    
-    time_started_flag = TRUE;
-    return ptNoError;
-}
-
-
-PtError Pt_Stop()
-{
-    /* printf("Pt_Stop called\n"); */
-    pt_callback_proc_id++;
-    time_started_flag = FALSE;
-    return ptNoError;
-}
-
-
-int Pt_Started()
-{
-    return time_started_flag;
-}
-
-
-PtTimestamp Pt_Time()
-{
-    UInt64 clock_time, nsec_time;
-    clock_time = AudioGetCurrentHostTime() - start_time;
-    nsec_time = AudioConvertHostTimeToNanos(clock_time);
-    return (PtTimestamp)(nsec_time / NSEC_PER_MSEC);
-}
-
-
-void Pt_Sleep(long duration)
-{
-    usleep(duration * 1000);
-}
+/* ptmacosx.c -- portable timer implementation for mac os x */++#include <stdlib.h>+#include <stdio.h>+#include <CoreAudio/HostTime.h>++#import <mach/mach.h>+#import <mach/mach_error.h>+#import <mach/mach_time.h>+#import <mach/clock.h>+#include <unistd.h>+#include <AvailabilityMacros.h>++#include "porttime.h"+#include "sys/time.h"+#include "pthread.h"++#ifndef NSEC_PER_MSEC+#define NSEC_PER_MSEC 1000000+#endif+#define THREAD_IMPORTANCE 63++/* QOS headers are available as of macOS 10.10 */+#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000+#include "sys/qos.h"+#define HAVE_APPLE_QOS 1+#else+#undef HAVE_APPLE_QOS+#endif++static int time_started_flag = FALSE;+static UInt64 start_time;+static pthread_t pt_thread_pid;++/* note that this is static data -- we only need one copy */+typedef struct {+    int id;+    int resolution;+    PtCallback *callback;+    void *userData;+} pt_callback_parameters;++static int pt_callback_proc_id = 0;++static void *Pt_CallbackProc(void *p)+{+    pt_callback_parameters *parameters = (pt_callback_parameters *) p;+    int mytime = 1;++    kern_return_t error;+    thread_extended_policy_data_t extendedPolicy;+    thread_precedence_policy_data_t precedencePolicy;++    extendedPolicy.timeshare = 0;+    error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY,+                              (thread_policy_t)&extendedPolicy,+                              THREAD_EXTENDED_POLICY_COUNT);+    if (error != KERN_SUCCESS) {+        mach_error("Couldn't set thread timeshare policy", error);+    }++    precedencePolicy.importance = THREAD_IMPORTANCE;+    error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY,+                              (thread_policy_t)&precedencePolicy,+                              THREAD_PRECEDENCE_POLICY_COUNT);+    if (error != KERN_SUCCESS) {+        mach_error("Couldn't set thread precedence policy", error);+    }+    +    /* Most important, set real-time constraints.+       Define the guaranteed and max fraction of time for the audio thread.+       These "duty cycle" values can range from 0 to 1.  A value of 0.5+       means the scheduler would give half the time to the thread.+       These values have empirically been found to yield good behavior.+       Good means that audio performance is high and other threads won't starve.+    */+    const double kGuaranteedAudioDutyCycle = 0.75;+    const double kMaxAudioDutyCycle = 0.85;+    +    /* Define constants determining how much time the audio thread can+       use in a given time quantum.  All times are in milliseconds.+    */+    /* About 128 frames @44.1KHz */+    const double kTimeQuantum = 2.9;+    +    /* Time guaranteed each quantum. */+    const double kAudioTimeNeeded = kGuaranteedAudioDutyCycle * kTimeQuantum;+    +    /* Maximum time each quantum. */+    const double kMaxTimeAllowed = kMaxAudioDutyCycle * kTimeQuantum;+    +    /* Get the conversion factor from milliseconds to absolute time+       which is what the time-constraints call needs.+    */+    mach_timebase_info_data_t tb_info;+    mach_timebase_info(&tb_info);+    double ms_to_abs_time =+    ((double)tb_info.denom / (double)tb_info.numer) * 1000000;+    +    thread_time_constraint_policy_data_t time_constraints;+    time_constraints.period = (uint32_t)(kTimeQuantum * ms_to_abs_time);+    time_constraints.computation = (uint32_t)(kAudioTimeNeeded * ms_to_abs_time);+    time_constraints.constraint = (uint32_t)(kMaxTimeAllowed * ms_to_abs_time);+    time_constraints.preemptible = 0;+    +    error = thread_policy_set(mach_thread_self(),+                               THREAD_TIME_CONSTRAINT_POLICY,+                               (thread_policy_t)&time_constraints,+                               THREAD_TIME_CONSTRAINT_POLICY_COUNT);+    if (error != KERN_SUCCESS) {+        mach_error("Couldn't set thread precedence policy", error);+    }++    /* to kill a process, just increment the pt_callback_proc_id */+    /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id,+              parameters->id); */+    while (pt_callback_proc_id == parameters->id) {+        /* wait for a multiple of resolution ms */+        UInt64 wait_time;+        int delay = mytime++ * parameters->resolution - Pt_Time();+        PtTimestamp timestamp;+        if (delay < 0) delay = 0;+        wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC);+        wait_time += AudioGetCurrentHostTime();+        mach_wait_until(wait_time);+        timestamp = Pt_Time();+        (*(parameters->callback))(timestamp, parameters->userData);+    }+    free(parameters);+    return NULL;+}+++PtError Pt_Start(int resolution, PtCallback *callback, void *userData)+{+    if (time_started_flag) return ptAlreadyStarted;+    start_time = AudioGetCurrentHostTime();+    +    if (callback) {+        int res;+        pt_callback_parameters *parms;++        parms = (pt_callback_parameters *) malloc(sizeof(pt_callback_parameters));+        if (!parms) return ptInsufficientMemory;+        parms->id = pt_callback_proc_id;+        parms->resolution = resolution;+        parms->callback = callback;+        parms->userData = userData;+        +#ifdef HAVE_APPLE_QOS+        pthread_attr_t qosAttribute;+        pthread_attr_init(&qosAttribute);+        pthread_attr_set_qos_class_np(&qosAttribute, +                                      QOS_CLASS_USER_INTERACTIVE, 0);++        res = pthread_create(&pt_thread_pid, &qosAttribute, Pt_CallbackProc, +                             parms);+#else+        res = pthread_create(&pt_thread_pid, NULL, Pt_CallbackProc, parms);+#endif+        +        struct sched_param sp;+        memset(&sp, 0, sizeof(struct sched_param));+        sp.sched_priority = sched_get_priority_max(SCHED_RR);+        if (pthread_setschedparam(pthread_self(), SCHED_RR, &sp)  == -1) {+            return ptHostError;+        }+        +        if (res != 0) return ptHostError;+    }+    +    time_started_flag = TRUE;+    return ptNoError;+}+++PtError Pt_Stop(void)+{+    /* printf("Pt_Stop called\n"); */+    pt_callback_proc_id++;+    pthread_join(pt_thread_pid, NULL);+    time_started_flag = FALSE;+    return ptNoError;+}+++int Pt_Started(void)+{+    return time_started_flag;+}+++PtTimestamp Pt_Time(void)+{+    UInt64 clock_time, nsec_time;+    clock_time = AudioGetCurrentHostTime() - start_time;+    nsec_time = AudioConvertHostTimeToNanos(clock_time);+    return (PtTimestamp)(nsec_time / NSEC_PER_MSEC);+}+++void Pt_Sleep(int32_t duration)+{+    usleep(duration * 1000);+}
portmidi/porttime/ptwinmm.c view
@@ -1,70 +1,70 @@-/* ptwinmm.c -- portable timer implementation for win32 */
-
-
-#include "porttime.h"
-#include "windows.h"
-#include "time.h"
-
-
-TIMECAPS caps;
-
-static long time_offset = 0;
-static int time_started_flag = FALSE;
-static long time_resolution;
-static MMRESULT timer_id;
-static PtCallback *time_callback;
-
-void CALLBACK winmm_time_callback(UINT uID, UINT uMsg, DWORD dwUser, 
-                                  DWORD dw1, DWORD dw2)
-{
-    (*time_callback)(Pt_Time(), (void *) dwUser);
-}
- 
-
-PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
-{
-    if (time_started_flag) return ptAlreadyStarted;
-    timeBeginPeriod(resolution);
-    time_resolution = resolution;
-    time_offset = timeGetTime();
-    time_started_flag = TRUE;
-    time_callback = callback;
-    if (callback) {
-        timer_id = timeSetEvent(resolution, 1, winmm_time_callback, 
-            (DWORD) userData, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
-        if (!timer_id) return ptHostError;
-    }
-    return ptNoError;
-}
-
-
-PtError Pt_Stop()
-{
-    if (!time_started_flag) return ptAlreadyStopped;
-    if (time_callback && timer_id) {
-        timeKillEvent(timer_id);
-        time_callback = NULL;
-        timer_id = 0;
-    }
-    time_started_flag = FALSE;
-    timeEndPeriod(time_resolution);
-    return ptNoError;
-}
-
-
-int Pt_Started()
-{
-    return time_started_flag;
-}
-
-
-PtTimestamp Pt_Time()
-{
-    return timeGetTime() - time_offset;
-}
-
-
-void Pt_Sleep(long duration)
-{
-    Sleep(duration);
-}
+/* ptwinmm.c -- portable timer implementation for win32 */+++#include "porttime.h"+#include "windows.h"+#include "time.h"+++TIMECAPS caps;++static long time_offset = 0;+static int time_started_flag = FALSE;+static long time_resolution;+static MMRESULT timer_id;+static PtCallback *time_callback;++void CALLBACK winmm_time_callback(UINT uID, UINT uMsg, DWORD_PTR dwUser, +                                  DWORD_PTR dw1, DWORD_PTR dw2)+{+    (*time_callback)(Pt_Time(), (void *) dwUser);+}+ ++PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData)+{+    if (time_started_flag) return ptAlreadyStarted;+    timeBeginPeriod(resolution);+    time_resolution = resolution;+    time_offset = timeGetTime();+    time_started_flag = TRUE;+    time_callback = callback;+    if (callback) {+        timer_id = timeSetEvent(resolution, 1, winmm_time_callback, +            (DWORD_PTR) userData, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);+        if (!timer_id) return ptHostError;+    }+    return ptNoError;+}+++PMEXPORT PtError Pt_Stop(void)+{+    if (!time_started_flag) return ptAlreadyStopped;+    if (time_callback && timer_id) {+        timeKillEvent(timer_id);+        time_callback = NULL;+        timer_id = 0;+    }+    time_started_flag = FALSE;+    timeEndPeriod(time_resolution);+    return ptNoError;+}+++PMEXPORT int Pt_Started(void)+{+    return time_started_flag;+}+++PMEXPORT PtTimestamp Pt_Time(void)+{+    return timeGetTime() - time_offset;+}+++PMEXPORT void Pt_Sleep(int32_t duration)+{+    Sleep(duration);+}