diff --git a/lib/usbif/__init__.py b/lib/usbif/__init__.py index 9893321..f756059 100644 --- a/lib/usbif/__init__.py +++ b/lib/usbif/__init__.py @@ -222,9 +222,14 @@ def _close(self): pass def _check(self, want): + # Two comparisons rather than `in (want, INOUT)`: the tuple is built + # on every call, and read() is polled every few milliseconds by apps + # that also move audio, where the garbage it leaves forces a + # collection (tens of milliseconds on a large heap) every second or so. if not self.is_open: raise OSError("MIDI port is closed") - if self.direction not in (want, INOUT): + direction = self.direction + if direction != want and direction != INOUT: raise OSError( "MIDI port {!r} is {}-only".format(self.info.name, self.direction) ) diff --git a/lib/usbif/native_usb.py b/lib/usbif/native_usb.py index bab4d72..6aeee1c 100644 --- a/lib/usbif/native_usb.py +++ b/lib/usbif/native_usb.py @@ -346,15 +346,20 @@ def uac_pump_rate(self, *args): rate the pump's I2S wire is clocked at, and how often it followed.""" return _require().uac_pump_rate(*args) - def uac_available(self, *args): - return _require().uac_available(*args) + # uac_available() and uac_read() take fixed arguments, unlike the + # wrappers around them: they are what a Python pump polls every + # millisecond, and `*args` builds a tuple per call. That garbage forces a + # collection every so often, which on a large heap stalls the pump for + # longer than the 4.7 ms the full-speed FIFO holds. + def uac_available(self): + return _require().uac_available() def uac_volume(self, *args): """The host's playback volume and mute, as the host set them.""" return _require().uac_volume(*args) - def uac_read(self, *args): - return _require().uac_read(*args) + def uac_read(self, buf=None): + return _require().uac_read(buf) def uac_stats(self, *args): return _require().uac_stats(*args)