From a1e5d634505e42ba0bb50c1da1d746ef0c6f29c3 Mon Sep 17 00:00:00 2001 From: Florian Festi Date: Tue, 10 Jan 2017 18:10:42 +0100 Subject: [PATCH 01/28] Py3k compat: make imports of sub modules relativ --- src/__init__.py | 9 +++++---- src/fileio.py | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/__init__.py b/src/__init__.py index 0092665..4c0b995 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,5 +1,6 @@ -from containers import * -from events import * from struct import unpack, pack -from util import * -from fileio import * + +from .containers import * +from .events import * +from .util import * +from .fileio import * diff --git a/src/fileio.py b/src/fileio.py index c709819..fa7ba6a 100644 --- a/src/fileio.py +++ b/src/fileio.py @@ -1,10 +1,11 @@ from warnings import * -from containers import * -from events import * from struct import unpack, pack -from constants import * -from util import * + +from .containers import * +from .events import * +from .constants import * +from .util import * class FileReader(object): def read(self, midifile): From a4ef00686d0e44d2a9f78cae9716bf1d93183277 Mon Sep 17 00:00:00 2001 From: Florian Festi Date: Tue, 10 Jan 2017 18:12:35 +0100 Subject: [PATCH 02/28] Py3k compat: Remove errous __slot__ definitions Those properties are within the classes and don't need space in the instances. Py3k complaints about them as the properies shadow the slots. --- src/events.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/events.py b/src/events.py index 24f41eb..2053e5b 100644 --- a/src/events.py +++ b/src/events.py @@ -111,7 +111,6 @@ def is_event(cls, statusmsg): """ class NoteEvent(Event): - __slots__ = ['pitch', 'velocity'] length = 2 def get_pitch(self): @@ -152,7 +151,6 @@ def set_value(self, val): value = property(get_value, set_value) class ControlChangeEvent(Event): - __slots__ = ['control', 'value'] statusmsg = 0xB0 length = 2 name = 'Control Change' @@ -170,7 +168,6 @@ def get_value(self): value = property(get_value, set_value) class ProgramChangeEvent(Event): - __slots__ = ['value'] statusmsg = 0xC0 length = 1 name = 'Program Change' @@ -182,7 +179,6 @@ def get_value(self): value = property(get_value, set_value) class ChannelAfterTouchEvent(Event): - __slots__ = ['value'] statusmsg = 0xD0 length = 1 name = 'Channel After Touch' @@ -194,7 +190,6 @@ def get_value(self): value = property(get_value, set_value) class PitchWheelEvent(Event): - __slots__ = ['pitch'] statusmsg = 0xE0 length = 2 name = 'Pitch Wheel' @@ -302,7 +297,6 @@ class EndOfTrackEvent(MetaEvent): metacommand = 0x2F class SetTempoEvent(MetaEvent): - __slots__ = ['bpm', 'mpqn'] name = 'Set Tempo' metacommand = 0x51 length = 3 @@ -315,7 +309,7 @@ def get_bpm(self): def get_mpqn(self): assert(len(self.data) == 3) - vals = [self.data[x] << (16 - (8 * x)) for x in xrange(3)] + vals = [self.data[x] << (16 - (8 * x)) for x in range(3)] return sum(vals) def set_mpqn(self, val): self.data = [(val >> (16 - (8 * x)) & 0xFF) for x in range(3)] @@ -326,7 +320,6 @@ class SmpteOffsetEvent(MetaEvent): metacommand = 0x54 class TimeSignatureEvent(MetaEvent): - __slots__ = ['numerator', 'denominator', 'metronome', 'thirtyseconds'] name = 'Time Signature' metacommand = 0x58 length = 4 @@ -356,7 +349,6 @@ def set_thirtyseconds(self, val): thirtyseconds = property(get_thirtyseconds, set_thirtyseconds) class KeySignatureEvent(MetaEvent): - __slots__ = ['alternatives', 'minor'] name = 'Key Signature' metacommand = 0x59 length = 2 From 2db4b816f06303c1015c260ebcb1a5eaa9b4fed4 Mon Sep 17 00:00:00 2001 From: Florian Festi Date: Wed, 11 Jan 2017 17:52:44 +0100 Subject: [PATCH 03/28] Py3k compat: Use with_metaclass for compatible meta classing --- src/events.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/events.py b/src/events.py index 2053e5b..73c857e 100644 --- a/src/events.py +++ b/src/events.py @@ -1,4 +1,5 @@ import math +from future.utils import with_metaclass class EventRegistry(object): Events = {} @@ -19,17 +20,18 @@ def register_event(cls, event, bases): register_event = classmethod(register_event) -class AbstractEvent(object): +class RegisterEventMeta(type): + def __init__(cls, name, bases, dict): + if name not in ['AbstractEvent', 'Event', 'MetaEvent', 'NoteEvent', + 'MetaEventWithText']: + EventRegistry.register_event(cls, bases) + +class AbstractEvent(with_metaclass(RegisterEventMeta,object)): __slots__ = ['tick', 'data'] name = "Generic MIDI Event" length = 0 statusmsg = 0x0 - class __metaclass__(type): - def __init__(cls, name, bases, dict): - if name not in ['AbstractEvent', 'Event', 'MetaEvent', 'NoteEvent', - 'MetaEventWithText']: - EventRegistry.register_event(cls, bases) def __init__(self, **kw): if type(self.length) == int: From 1fc0bd6c861029daad04952cd0fe180b54a9bcca Mon Sep 17 00:00:00 2001 From: Florian Festi Date: Thu, 12 Jan 2017 09:42:08 +0100 Subject: [PATCH 04/28] Py3k compat: Instantiate Exceptions and use repr() instead of backticks --- src/events.py | 2 +- src/fileio.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/events.py b/src/events.py index 73c857e..f6f3099 100644 --- a/src/events.py +++ b/src/events.py @@ -16,7 +16,7 @@ def register_event(cls, event, bases): "Event %s already registered" % event.name cls.MetaEvents[event.metacommand] = event else: - raise ValueError, "Unknown bases class in event type: "+event.name + raise ValueError("Unknown bases class in event type: ", event.name) register_event = classmethod(register_event) diff --git a/src/fileio.py b/src/fileio.py index fa7ba6a..2bea1e5 100644 --- a/src/fileio.py +++ b/src/fileio.py @@ -18,7 +18,7 @@ def parse_file_header(self, midifile): # First four bytes are MIDI header magic = midifile.read(4) if magic != 'MThd': - raise TypeError, "Bad header in MIDI file." + raise TypeError("Bad header in MIDI file.") # next four bytes are header size # next two bytes specify the format version # next two bytes specify the number of tracks @@ -39,7 +39,7 @@ def parse_track_header(self, midifile): # First four bytes are Track header magic = midifile.read(4) if magic != 'MTrk': - raise TypeError, "Bad track header in MIDI file: " + magic + raise TypeError("Bad track header in MIDI file: " + magic) # next four bytes are track size trksz = unpack(">L", midifile.read(4))[0] return trksz @@ -64,7 +64,7 @@ def parse_midi_event(self, trackdata): if MetaEvent.is_event(stsmsg): cmd = ord(trackdata.next()) if cmd not in EventRegistry.MetaEvents: - warn("Unknown Meta MIDI Event: " + `cmd`, Warning) + warn("Unknown Meta MIDI Event: " + repr(cmd), Warning) cls = UnknownMetaEvent else: cls = EventRegistry.MetaEvents[cmd] @@ -98,7 +98,7 @@ def parse_midi_event(self, trackdata): channel = self.RunningStatus & 0x0F data = [ord(trackdata.next()) for x in range(cls.length)] return cls(tick=tick, channel=channel, data=data) - raise Warning, "Unknown MIDI Event: " + `stsmsg` + raise Warning("Unknown MIDI Event: " + repr(stsmsg)) class FileWriter(object): def write(self, midifile, pattern): @@ -147,7 +147,7 @@ def encode_midi_event(self, event): ret += chr(event.statusmsg | event.channel) ret += str.join('', map(chr, event.data)) else: - raise ValueError, "Unknown MIDI Event: " + str(event) + raise ValueError("Unknown MIDI Event: " + str(event)) return ret def write_midifile(midifile, pattern): From dcd62bc09be775348533dd5425af3a55cd309e8e Mon Sep 17 00:00:00 2001 From: Florian Festi Date: Thu, 12 Jan 2017 09:51:38 +0100 Subject: [PATCH 05/28] Py3k compat: make data read a bytearray in both Python2 and Python3 This gets rid of all those annoying ord() calls Use next() instead of .next() Use b"" where comparing with the raw data --- src/fileio.py | 20 ++++++++++---------- src/sequencer.py | 10 +++++----- src/util.py | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/fileio.py b/src/fileio.py index 2bea1e5..d3b62ac 100644 --- a/src/fileio.py +++ b/src/fileio.py @@ -17,7 +17,7 @@ def read(self, midifile): def parse_file_header(self, midifile): # First four bytes are MIDI header magic = midifile.read(4) - if magic != 'MThd': + if magic != b'MThd': raise TypeError("Bad header in MIDI file.") # next four bytes are header size # next two bytes specify the format version @@ -38,7 +38,7 @@ def parse_file_header(self, midifile): def parse_track_header(self, midifile): # First four bytes are Track header magic = midifile.read(4) - if magic != 'MTrk': + if magic != b'MTrk': raise TypeError("Bad track header in MIDI file: " + magic) # next four bytes are track size trksz = unpack(">L", midifile.read(4))[0] @@ -47,7 +47,7 @@ def parse_track_header(self, midifile): def parse_track(self, midifile, track): self.RunningStatus = None trksz = self.parse_track_header(midifile) - trackdata = iter(midifile.read(trksz)) + trackdata = iter(bytearray(midifile.read(trksz))) while True: try: event = self.parse_midi_event(trackdata) @@ -59,23 +59,23 @@ def parse_midi_event(self, trackdata): # first datum is varlen representing delta-time tick = read_varlen(trackdata) # next byte is status message - stsmsg = ord(trackdata.next()) + stsmsg = next(trackdata) # is the event a MetaEvent? if MetaEvent.is_event(stsmsg): - cmd = ord(trackdata.next()) + cmd = next(trackdata) if cmd not in EventRegistry.MetaEvents: warn("Unknown Meta MIDI Event: " + repr(cmd), Warning) cls = UnknownMetaEvent else: cls = EventRegistry.MetaEvents[cmd] datalen = read_varlen(trackdata) - data = [ord(trackdata.next()) for x in range(datalen)] + data = [next(trackdata) for x in range(datalen)] return cls(tick=tick, data=data, metacommand=cmd) # is this event a Sysex Event? elif SysexEvent.is_event(stsmsg): data = [] while True: - datum = ord(trackdata.next()) + datum = next(trackdata) if datum == 0xF7: break data.append(datum) @@ -90,13 +90,13 @@ def parse_midi_event(self, trackdata): cls = EventRegistry.Events[key] channel = self.RunningStatus & 0x0F data.append(stsmsg) - data += [ord(trackdata.next()) for x in range(cls.length - 1)] + data += [next(trackdata) for x in range(cls.length - 1)] return cls(tick=tick, channel=channel, data=data) else: self.RunningStatus = stsmsg cls = EventRegistry.Events[key] channel = self.RunningStatus & 0x0F - data = [ord(trackdata.next()) for x in range(cls.length)] + data = [next(trackdata) for x in range(cls.length)] return cls(tick=tick, channel=channel, data=data) raise Warning("Unknown MIDI Event: " + repr(stsmsg)) @@ -123,7 +123,7 @@ def write_track(self, midifile, track): midifile.write(buf) def encode_track_header(self, trklen): - return 'MTrk%s' % pack(">L", trklen) + return b'MTrk%s' % pack(">L", trklen) def encode_midi_event(self, event): ret = '' diff --git a/src/sequencer.py b/src/sequencer.py index c7842d1..2f51e81 100644 --- a/src/sequencer.py +++ b/src/sequencer.py @@ -50,9 +50,9 @@ def __init__(self, stream, window): self.ttpts.append(stream.endoftrack.tick) self.ttpts = iter(self.ttpts) # Setup next tempo timepoint - self.ttp = self.ttpts.next() + self.ttp = next(self.ttpts) self.tempomap = iter(self.stream.tempomap) - self.tempo = self.tempomap.next() + self.tempo = next(self.tempomap) self.endoftrack = False def __iter__(self): @@ -67,7 +67,7 @@ def __next_edge(self): # We're past the tempo-marker. oldttp = self.ttp try: - self.ttp = self.ttpts.next() + self.ttp = next(self.ttpts) except StopIteration: # End of Track! self.window_edge = self.ttp @@ -77,11 +77,11 @@ def __next_edge(self): # account the tempo change. msused = (oldttp - lastedge) * self.tempo.mpt msleft = self.window_length - msused - self.tempo = self.tempomap.next() + self.tempo = next(self.tempomap) ticksleft = msleft / self.tempo.mpt self.window_edge = ticksleft + self.tempo.tick - def next(self): + def __next__(self): ret = [] self.__next_edge() if self.leftover: diff --git a/src/util.py b/src/util.py index 688730b..25bef1e 100644 --- a/src/util.py +++ b/src/util.py @@ -3,7 +3,7 @@ def read_varlen(data): NEXTBYTE = 1 value = 0 while NEXTBYTE: - chr = ord(data.next()) + chr = next(data) # is the hi-bit set? if not (chr & 0x80): # no next BYTE From e78bd9aceff173df7d91ca87d3ac2cd1b85b8815 Mon Sep 17 00:00:00 2001 From: Florian Festi Date: Thu, 12 Jan 2017 13:11:41 +0100 Subject: [PATCH 06/28] Py3k compat: wrap ranges with list() Minor reformat --- src/constants.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/constants.py b/src/constants.py index fb329b7..96c6816 100644 --- a/src/constants.py +++ b/src/constants.py @@ -3,13 +3,13 @@ ## OCTAVE_MAX_VALUE = 12 -OCTAVE_VALUES = range( OCTAVE_MAX_VALUE ) +OCTAVE_VALUES = list(range( OCTAVE_MAX_VALUE)) -NOTE_NAMES = ['C','Cs','D','Ds','E','F','Fs','G','Gs','A','As','B'] +NOTE_NAMES = ['C', 'Cs', 'D', 'Ds', 'E', 'F', 'Fs', 'G', 'Gs', 'A', 'As', 'B'] WHITE_KEYS = [0, 2, 4, 5, 7, 9, 11] BLACK_KEYS = [1, 3, 6, 8, 10] NOTE_PER_OCTAVE = len( NOTE_NAMES ) -NOTE_VALUES = range( OCTAVE_MAX_VALUE * NOTE_PER_OCTAVE ) +NOTE_VALUES = list(range( OCTAVE_MAX_VALUE * NOTE_PER_OCTAVE)) NOTE_NAME_MAP_FLAT = {} NOTE_VALUE_MAP_FLAT = [] NOTE_NAME_MAP_SHARP = {} From b22f61f451dc9a7a56dfa6cf78872b3d279ee453 Mon Sep 17 00:00:00 2001 From: Florian Festi Date: Thu, 12 Jan 2017 15:26:56 +0100 Subject: [PATCH 07/28] Py3k compat: Change Filewriter to use bytes and bytearrays Ask for read() and write() attribute to avoid check for unicode which does not exist in Python3. --- src/fileio.py | 26 +++++++++++++------------- src/util.py | 23 +++++------------------ 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/src/fileio.py b/src/fileio.py index d3b62ac..4841ae5 100644 --- a/src/fileio.py +++ b/src/fileio.py @@ -112,10 +112,10 @@ def write_file_header(self, midifile, pattern): pattern.format, len(pattern), pattern.resolution) - midifile.write('MThd%s' % packdata) + midifile.write(b'MThd' + packdata) def write_track(self, midifile, track): - buf = '' + buf = b'' self.RunningStatus = None for event in track: buf += self.encode_midi_event(event) @@ -123,41 +123,41 @@ def write_track(self, midifile, track): midifile.write(buf) def encode_track_header(self, trklen): - return b'MTrk%s' % pack(">L", trklen) + return b'MTrk' + pack(">L", trklen) def encode_midi_event(self, event): - ret = '' + ret = bytearray() ret += write_varlen(event.tick) # is the event a MetaEvent? if isinstance(event, MetaEvent): - ret += chr(event.statusmsg) + chr(event.metacommand) + ret += bytearray(event.statusmsg + event.metacommand) ret += write_varlen(len(event.data)) - ret += str.join('', map(chr, event.data)) + ret += bytearray(event.data) # is this event a Sysex Event? elif isinstance(event, SysexEvent): - ret += chr(0xF0) - ret += str.join('', map(chr, event.data)) - ret += chr(0xF7) + ret.append(0xF0) + ret += bytearray(event.data) + ret.append(0xF7) # not a Meta MIDI event or a Sysex event, must be a general message elif isinstance(event, Event): if not self.RunningStatus or \ self.RunningStatus.statusmsg != event.statusmsg or \ self.RunningStatus.channel != event.channel: self.RunningStatus = event - ret += chr(event.statusmsg | event.channel) - ret += str.join('', map(chr, event.data)) + ret.append(event.statusmsg | event.channel) + ret += bytearray(event.data) else: raise ValueError("Unknown MIDI Event: " + str(event)) return ret def write_midifile(midifile, pattern): - if type(midifile) in (str, unicode): + if not hasattr(midifile, "write"): midifile = open(midifile, 'wb') writer = FileWriter() return writer.write(midifile, pattern) def read_midifile(midifile): - if type(midifile) in (str, unicode): + if not hasattr(midifile, "read"): midifile = open(midifile, 'rb') reader = FileReader() return reader.read(midifile) diff --git a/src/util.py b/src/util.py index 25bef1e..8f4f581 100644 --- a/src/util.py +++ b/src/util.py @@ -17,22 +17,9 @@ def read_varlen(data): return value def write_varlen(value): - chr1 = chr(value & 0x7F) - value >>= 7 - if value: - chr2 = chr((value & 0x7F) | 0x80) + result = bytearray() + while value > 0x7F: + result.append((value & 0x7F) | 0x80) value >>= 7 - if value: - chr3 = chr((value & 0x7F) | 0x80) - value >>= 7 - if value: - chr4 = chr((value & 0x7F) | 0x80) - res = chr4 + chr3 + chr2 + chr1 - else: - res = chr3 + chr2 + chr1 - else: - res = chr2 + chr1 - else: - res = chr1 - return res - + result.append(value) + return result From 5e85385d94b29a09ba10a2ae5277703d2f6d9179 Mon Sep 17 00:00:00 2001 From: Florian Festi Date: Thu, 12 Jan 2017 17:09:29 +0100 Subject: [PATCH 08/28] Py3k compat: Use rich comparisons May need some love --- src/events.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/events.py b/src/events.py index f6f3099..60260dd 100644 --- a/src/events.py +++ b/src/events.py @@ -43,10 +43,18 @@ def __init__(self, **kw): for key in kw: setattr(self, key, kw[key]) - def __cmp__(self, other): - if self.tick < other.tick: return -1 - elif self.tick > other.tick: return 1 - return cmp(self.data, other.data) + def __lt__(self, other): + if self.tick < other.tick: + return True + return self.data < other.data + + def __eq__(self, other): + return (self.__class__ is other.__class__ and + self.tick == other.tick and + self.data == other.data) + + def __ne__(self, other): + return not self.__eq__(other) def __baserepr__(self, keys=[]): keys = ['tick'] + keys + ['data'] @@ -77,10 +85,14 @@ def copy(self, **kw): _kw.update(kw) return self.__class__(**_kw) - def __cmp__(self, other): - if self.tick < other.tick: return -1 - elif self.tick > other.tick: return 1 - return 0 + def __lt__(self, other): + return (super(Event, self).__lt__(other) or + (super(Event, self).__eq__(other) and + self.channel < other.channel)) + + def __eq__(self, other): + return super(Event, self).__eq__(other) and \ + self.channel == other.channel def __repr__(self): return self.__baserepr__(['channel']) From 7ae6ec36d43aacf33c09fbea8469fab390011f92 Mon Sep 17 00:00:00 2001 From: Florian Festi Date: Thu, 12 Jan 2017 17:36:22 +0100 Subject: [PATCH 09/28] Py3k compat: Use print_function from __future__ --- examples/example_1.py | 3 ++- examples/example_2.py | 3 ++- scripts/mididump.py | 6 ++++-- scripts/mididumphw.py | 3 ++- scripts/midilisten.py | 6 ++++-- scripts/midiplay.py | 6 ++++-- setup.py | 3 ++- src/sequencer_alsa/sequencer.py | 5 +++-- src/sequencer_osx/test.py | 6 ++++-- 9 files changed, 27 insertions(+), 14 deletions(-) diff --git a/examples/example_1.py b/examples/example_1.py index 60acb87..d311ac6 100644 --- a/examples/example_1.py +++ b/examples/example_1.py @@ -1,3 +1,4 @@ +from __future__ import print_function import midi # Instantiate a MIDI Pattern (contains a list of tracks) pattern = midi.Pattern() @@ -15,6 +16,6 @@ eot = midi.EndOfTrackEvent(tick=1) track.append(eot) # Print out the pattern -print pattern +print(pattern) # Save the pattern to disk midi.write_midifile("example.mid", pattern) diff --git a/examples/example_2.py b/examples/example_2.py index ecc4a87..2306eda 100644 --- a/examples/example_2.py +++ b/examples/example_2.py @@ -1,3 +1,4 @@ +from __future__ import print_function import midi pattern = midi.read_midifile("example.mid") -print pattern +print(pattern) diff --git a/scripts/mididump.py b/scripts/mididump.py index 1497a3d..cca828b 100644 --- a/scripts/mididump.py +++ b/scripts/mididump.py @@ -1,4 +1,6 @@ #!/usr/bin/env python +from __future__ import print_function + """ Print a description of a MIDI file. """ @@ -6,9 +8,9 @@ import sys if len(sys.argv) != 2: - print "Usage: {0} ".format(sys.argv[0]) + print("Usage: {0} ".format(sys.argv[0])) sys.exit(2) midifile = sys.argv[1] pattern = midi.read_midifile(midifile) -print repr(pattern) +print(repr(pattern)) diff --git a/scripts/mididumphw.py b/scripts/mididumphw.py index 42aef0b..a40cc69 100644 --- a/scripts/mididumphw.py +++ b/scripts/mididumphw.py @@ -2,8 +2,9 @@ """ Print a description of the available devices. """ +from __future__ import print_function import midi.sequencer as sequencer s = sequencer.SequencerHardware() -print s +print(s) diff --git a/scripts/midilisten.py b/scripts/midilisten.py index b86ee5c..2cb44ef 100644 --- a/scripts/midilisten.py +++ b/scripts/midilisten.py @@ -2,13 +2,15 @@ """ Attach to a MIDI device and print events to standard output. """ +from __future__ import print_function + import sys import time import midi import midi.sequencer as sequencer if len(sys.argv) != 3: - print "Usage: {0} ".format(sys.argv[0]) + print("Usage: {0} ".format(sys.argv[0])) exit(2) client = sys.argv[1] @@ -21,4 +23,4 @@ while True: event = seq.event_read() if event is not None: - print event + print(event) diff --git a/scripts/midiplay.py b/scripts/midiplay.py index 3fb6121..2169d79 100644 --- a/scripts/midiplay.py +++ b/scripts/midiplay.py @@ -2,13 +2,15 @@ """ Attach to a MIDI device and send the contents of a MIDI file to it. """ +from __future__ import print_function + import sys import time import midi import midi.sequencer as sequencer if len(sys.argv) != 4: - print "Usage: {0} ".format(sys.argv[0]) + print("Usage: {0} ".format(sys.argv[0])) exit(2) client = sys.argv[1] @@ -45,4 +47,4 @@ seq.drain() time.sleep(.5) -print 'The end?' +print('The end?') diff --git a/setup.py b/setup.py index bb5b591..29879ca 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +from __future__ import print_function import os from setuptools import setup, Extension import setuptools.command.install @@ -62,7 +63,7 @@ def configure_platform(): setup_alsa(ns) pass else: - print "No sequencer available for '%s' platform." % platform + print("No sequencer available for '%s' platform." % platform) return ns if __name__ == "__main__": diff --git a/src/sequencer_alsa/sequencer.py b/src/sequencer_alsa/sequencer.py index 9a9c3ef..cf979e7 100644 --- a/src/sequencer_alsa/sequencer.py +++ b/src/sequencer_alsa/sequencer.py @@ -1,3 +1,4 @@ +from __future__ import print_function import select import sequencer_alsa as S import midi @@ -204,7 +205,7 @@ def output_pending(self): ## EVENT HANDLERS ## def event_write(self, event, direct=False, relative=False, tick=False): - #print event.__class__, event + #print(event.__class__, event) ## Event Filter if isinstance(event, midi.EndOfTrackEvent): return @@ -269,7 +270,7 @@ def event_write(self, event, direct=False, relative=False, tick=False): seqev.data.control.value = event.pitch ## Unknown else: - print "Warning :: Unknown event type: %s" % event + print("Warning :: Unknown event type: %s" % event) return None err = S.snd_seq_event_output(self.client, seqev) diff --git a/src/sequencer_osx/test.py b/src/sequencer_osx/test.py index 97873e0..e39c9b5 100644 --- a/src/sequencer_osx/test.py +++ b/src/sequencer_osx/test.py @@ -1,9 +1,11 @@ +from __future__ import print_function import sequencer_osx -print "MIDIGetNumberOfDevices:", sequencer_osx._MIDIGetNumberOfDevices() + +print("MIDIGetNumberOfDevices:", sequencer_osx._MIDIGetNumberOfDevices()) client = sequencer_osx._MIDIClientCreate("python") endpoint = sequencer_osx._MIDISourceCreate(client, "python-source") port = sequencer_osx._MIDIOutputPortCreate(client, "python-port") sequencer_osx._MIDIPortConnectSource(port, endpoint) -print client, endpoint, endpoint +print(client, endpoint, endpoint) raw_input() #sequencer_osx._MIDIClientDispose(handle) From 6d63385bb2dc717bb3d892a5cf6ce0ac1a1ade8f Mon Sep 17 00:00:00 2001 From: Florian Festi Date: Thu, 12 Jan 2017 17:40:17 +0100 Subject: [PATCH 10/28] Py3k compat: replace xrange by range --- src/containers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/containers.py b/src/containers.py index 006dbac..4a79b63 100644 --- a/src/containers.py +++ b/src/containers.py @@ -25,7 +25,7 @@ def __getitem__(self, item): if isinstance(item, slice): indices = item.indices(len(self)) return Pattern(resolution=self.resolution, format=self.format, - tracks=(super(Pattern, self).__getitem__(i) for i in xrange(*indices))) + tracks=(super(Pattern, self).__getitem__(i) for i in range(*indices))) else: return super(Pattern, self).__getitem__(item) @@ -58,7 +58,7 @@ def make_ticks_rel(self): def __getitem__(self, item): if isinstance(item, slice): indices = item.indices(len(self)) - return Track((super(Track, self).__getitem__(i) for i in xrange(*indices))) + return Track((super(Track, self).__getitem__(i) for i in range(*indices))) else: return super(Track, self).__getitem__(item) From 5db02c195f7bea156954f54cb343ce8b05f0fb07 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 20:37:30 +0300 Subject: [PATCH 11/28] Include own copy of with_metaclass() --- src/events.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/events.py b/src/events.py index 60260dd..acd8181 100644 --- a/src/events.py +++ b/src/events.py @@ -1,5 +1,4 @@ import math -from future.utils import with_metaclass class EventRegistry(object): Events = {} @@ -26,6 +25,23 @@ def __init__(cls, name, bases, dict): 'MetaEventWithText']: EventRegistry.register_event(cls, bases) + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(type): + + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + + @classmethod + def __prepare__(cls, name, this_bases): + return meta.__prepare__(name, bases) + return type.__new__(metaclass, 'temporary_class', (), {}) + + class AbstractEvent(with_metaclass(RegisterEventMeta,object)): __slots__ = ['tick', 'data'] name = "Generic MIDI Event" From a346920d90559daf6d298377d994f4527cf5ec4c Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 20:39:46 +0300 Subject: [PATCH 12/28] Add a tox.ini --- .gitignore | 3 +++ tox.ini | 8 ++++++++ 2 files changed, 11 insertions(+) create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index f656a0b..3657113 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ build *.pyc .coverage .*.sw? +.tox/ +*.egg-info/ +_sequencer_alsa.so diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..457f0a4 --- /dev/null +++ b/tox.ini @@ -0,0 +1,8 @@ +[tox] +envlist = py27,py33,py34,py35,py36 + +[testenv] +deps = + nose +commands = + nosetests tests From 637211512a0f53b638154e4e10bb442018fd81f4 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 20:50:15 +0300 Subject: [PATCH 13/28] Work around issue #129 so I can run tests --- tests/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.py b/tests/tests.py index 2dc9b2c..3f14e87 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -6,7 +6,7 @@ try: import midi.sequencer as sequencer -except ImportError: +except (ImportError, AttributeError): sequencer = None def get_sequencer_type(): From 7834abceb1be17bd988b04be7914d064d27b5c29 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 20:59:51 +0300 Subject: [PATCH 14/28] Fix byte ordering issues in write_varlen() --- src/util.py | 8 +++++--- tests/tests.py | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/util.py b/src/util.py index 8f4f581..c2161cf 100644 --- a/src/util.py +++ b/src/util.py @@ -18,8 +18,10 @@ def read_varlen(data): def write_varlen(value): result = bytearray() + hi_bit = 0 while value > 0x7F: - result.append((value & 0x7F) | 0x80) + result.append((value & 0x7F) | hi_bit) value >>= 7 - result.append(value) - return result + hi_bit = 0x80 + result.append(value | hi_bit) + return result[::-1] diff --git a/tests/tests.py b/tests/tests.py index 3f14e87..cd0c33f 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -20,7 +20,8 @@ def test_varlen(self): for inval in xrange(0, maxval, maxval / 1000): datum = midi.write_varlen(inval) outval = midi.read_varlen(iter(datum)) - self.assertEqual(inval, outval) + self.assertEqual(inval, outval, '0x%x -> %r -> 0x%x' % ( + inval, datum, outval)) def test_mary(self): midi.write_midifile("mary.mid", mary_test.MARY_MIDI) From 6e38251a29cbeb2a39599cdeeda9184b27fc26a7 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 21:21:00 +0300 Subject: [PATCH 15/28] Fix MIDI writing The tests now pass (again) on Python 2.7. --- src/fileio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fileio.py b/src/fileio.py index 4841ae5..8deaf8f 100644 --- a/src/fileio.py +++ b/src/fileio.py @@ -130,7 +130,7 @@ def encode_midi_event(self, event): ret += write_varlen(event.tick) # is the event a MetaEvent? if isinstance(event, MetaEvent): - ret += bytearray(event.statusmsg + event.metacommand) + ret += bytearray([event.statusmsg, event.metacommand]) ret += write_varlen(len(event.data)) ret += bytearray(event.data) # is this event a Sysex Event? From 8888c95a9d2325f5d52b5017130e852342f23803 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 21:25:59 +0300 Subject: [PATCH 16/28] Work around AttributeError: 'module' object has no attribute 'Sequencer' I'm getting that error on Python 3 and I don't quite understand how the sequencer module works here (src/sequencer.py doesn't define a Sequencer class!). --- tests/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.py b/tests/tests.py index cd0c33f..92e79b7 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -10,7 +10,7 @@ sequencer = None def get_sequencer_type(): - if sequencer == None: + if not hasattr(sequencer, 'Sequencer'): return None return sequencer.Sequencer.SEQUENCER_TYPE From 6e1c2a7ef7015a284ff039cbec1498b05890c460 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 21:26:48 +0300 Subject: [PATCH 17/28] Fix tests under Python 3 --- tests/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.py b/tests/tests.py index 92e79b7..12a2b1f 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -17,7 +17,7 @@ def get_sequencer_type(): class TestMIDI(unittest.TestCase): def test_varlen(self): maxval = 0x0FFFFFFF - for inval in xrange(0, maxval, maxval / 1000): + for inval in range(0, maxval, maxval // 1000): datum = midi.write_varlen(inval) outval = midi.read_varlen(iter(datum)) self.assertEqual(inval, outval, '0x%x -> %r -> 0x%x' % ( From 7aa5e7bf2ebf988f84b30115f909613d339ece63 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 21:27:35 +0300 Subject: [PATCH 18/28] Enable Python 3 on Travis --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index be38855..c825138 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,9 @@ language: python python: - 2.7 + - 3.4 + - 3.5 + - 3.6 before_install: - date -u - uname -a From db46eb15662a5e001d369de08571a597a8ae3a4e Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 21:38:28 +0300 Subject: [PATCH 19/28] Make comparison logic match git master (I don't know why Event comparison ignores everything except tick, seems like a bad idea TBH, but it's what master does so *shrug*) Note that these methods appear to be untested. --- src/events.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/events.py b/src/events.py index acd8181..d87134d 100644 --- a/src/events.py +++ b/src/events.py @@ -1,4 +1,6 @@ import math +from functools import total_ordering + class EventRegistry(object): Events = {} @@ -42,6 +44,7 @@ def __prepare__(cls, name, this_bases): return type.__new__(metaclass, 'temporary_class', (), {}) +@total_ordering class AbstractEvent(with_metaclass(RegisterEventMeta,object)): __slots__ = ['tick', 'data'] name = "Generic MIDI Event" @@ -60,17 +63,10 @@ def __init__(self, **kw): setattr(self, key, kw[key]) def __lt__(self, other): - if self.tick < other.tick: - return True - return self.data < other.data + return (self.tick, self.data) < (other.tick, other.data) def __eq__(self, other): - return (self.__class__ is other.__class__ and - self.tick == other.tick and - self.data == other.data) - - def __ne__(self, other): - return not self.__eq__(other) + return (self.tick, self.data) == (other.tick, other.data) def __baserepr__(self, keys=[]): keys = ['tick'] + keys + ['data'] @@ -86,6 +82,7 @@ def __repr__(self): return self.__baserepr__() +@total_ordering class Event(AbstractEvent): __slots__ = ['channel'] name = 'Event' @@ -102,13 +99,10 @@ def copy(self, **kw): return self.__class__(**_kw) def __lt__(self, other): - return (super(Event, self).__lt__(other) or - (super(Event, self).__eq__(other) and - self.channel < other.channel)) + return self.tick < other.tick def __eq__(self, other): - return super(Event, self).__eq__(other) and \ - self.channel == other.channel + return self.tick == other.tick def __repr__(self): return self.__baserepr__(['channel']) From 4ba6d3c67988b395e07c4d3d4774abd19f35acf4 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 21:42:56 +0300 Subject: [PATCH 20/28] Python 2 iterator protocol compatibility --- src/sequencer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sequencer.py b/src/sequencer.py index 2f51e81..14be6a7 100644 --- a/src/sequencer.py +++ b/src/sequencer.py @@ -96,3 +96,4 @@ def __next__(self): ret.append(event) return ret + next = __next__ From 2f7f9db45ca1a120ed6b5ab4d64d24815089b9a6 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 21:47:54 +0300 Subject: [PATCH 21/28] Use relative imports for the sequencer as well --- src/sequencer_alsa/__init__.py | 2 +- src/sequencer_alsa/sequencer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sequencer_alsa/__init__.py b/src/sequencer_alsa/__init__.py index f9ca70d..dcd2c6f 100644 --- a/src/sequencer_alsa/__init__.py +++ b/src/sequencer_alsa/__init__.py @@ -1,4 +1,4 @@ try: - from sequencer import * + from .sequencer import * except ImportError: pass diff --git a/src/sequencer_alsa/sequencer.py b/src/sequencer_alsa/sequencer.py index cf979e7..68da490 100644 --- a/src/sequencer_alsa/sequencer.py +++ b/src/sequencer_alsa/sequencer.py @@ -1,7 +1,7 @@ from __future__ import print_function import select -import sequencer_alsa as S import midi +from . import sequencer_alsa as S __SWIG_NS_SET__ = set(['__class__', '__del__', '__delattr__', '__dict__', '__doc__', '__getattr__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__swig_getmethods__', '__swig_setmethods__', '__weakref__', 'this', 'thisown']) From 3f2d8069123cc3d4834a93ca7822df6fbc739b49 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 21:51:23 +0300 Subject: [PATCH 22/28] Python 3: instantiate exceptions --- src/sequencer_alsa/sequencer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sequencer_alsa/sequencer.py b/src/sequencer_alsa/sequencer.py index 68da490..5ce50aa 100644 --- a/src/sequencer_alsa/sequencer.py +++ b/src/sequencer_alsa/sequencer.py @@ -73,7 +73,7 @@ def get_nonblock(self): def _error(self, errcode): strerr = S.snd_strerror(errcode) msg = "ALSAError[%d]: %s" % (errcode, strerr) - raise RuntimeError, msg + raise RuntimeError(msg) def _init_handle(self): ret = S.open_client(self.alsa_sequencer_name, From 0597affedf0f60d94472c4b8192b023b81615677 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 21:51:49 +0300 Subject: [PATCH 23/28] Python 3: there's no itervalues() any more This is suboptimal on Python 2, but I don't expect these dicts to have many values, so it should be OK. --- src/sequencer_alsa/sequencer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sequencer_alsa/sequencer.py b/src/sequencer_alsa/sequencer.py index 5ce50aa..a55d619 100644 --- a/src/sequencer_alsa/sequencer.py +++ b/src/sequencer_alsa/sequencer.py @@ -325,7 +325,7 @@ def add_port(self, port, name, caps): self._ports[name] = port def __iter__(self): - return self._ports.itervalues() + return iter(self._ports.values()) def __len__(self): return len(self._ports) @@ -361,7 +361,7 @@ def init(self): self._query_clients() def __iter__(self): - return self._clients.itervalues() + return iter(self._clients.values()) def __len__(self): return len(self._clients) From 12d5b404ff16d08333e7b73a74179b0daf546c91 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 22:33:10 +0300 Subject: [PATCH 24/28] Figure out SWIG exception raising --- src/sequencer_alsa/sequencer.py | 13 ++++--------- src/sequencer_alsa/sequencer_alsa.i | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/sequencer_alsa/sequencer.py b/src/sequencer_alsa/sequencer.py index a55d619..b9550a9 100644 --- a/src/sequencer_alsa/sequencer.py +++ b/src/sequencer_alsa/sequencer.py @@ -76,14 +76,10 @@ def _error(self, errcode): raise RuntimeError(msg) def _init_handle(self): - ret = S.open_client(self.alsa_sequencer_name, - self.alsa_sequencer_type, - self.alsa_sequencer_stream, - self.alsa_sequencer_mode) - if ret == None: - # XXX: global error - self._error(ret) - self.client = ret + self.client = S.open_client(self.alsa_sequencer_name, + self.alsa_sequencer_type, + self.alsa_sequencer_stream, + self.alsa_sequencer_mode) self.client_id = S.snd_seq_client_id(self.client) self.output_buffer_size = S.snd_seq_get_output_buffer_size(self.client) self.input_buffer_size = S.snd_seq_get_input_buffer_size(self.client) @@ -280,7 +276,6 @@ def event_write(self, event, direct=False, relative=False, tick=False): def event_read(self): ev = S.event_input(self.client) - if ev and (ev < 0): self._error(ev) if ev and ev.type in (S.SND_SEQ_EVENT_NOTEON, S.SND_SEQ_EVENT_NOTEOFF): if ev.type == S.SND_SEQ_EVENT_NOTEON: mev = midi.NoteOnEvent() diff --git a/src/sequencer_alsa/sequencer_alsa.i b/src/sequencer_alsa/sequencer_alsa.i index 1ee63af..1a0fc5f 100644 --- a/src/sequencer_alsa/sequencer_alsa.i +++ b/src/sequencer_alsa/sequencer_alsa.i @@ -14,7 +14,7 @@ open_client(const char *name, const char *type, int stream, int mode) err = snd_seq_open(&handle, type, stream, mode); if (err < 0) { - /* XXX: set global error */ + PyErr_SetString(PyExc_IOError, snd_strerror(err)); return NULL; } snd_seq_set_client_name(handle, name); @@ -39,8 +39,7 @@ event_input(snd_seq_t *handle) err = snd_seq_event_input(handle, &ev); if (err < 0) { - /* XXX: does SWIG prevent us from raising an exception? */ - /* PyErr_SetString(PyExc_IOError, snd_strerror(err)); */ + PyErr_SetString(PyExc_IOError, snd_strerror(err)); return NULL; } return ev; @@ -127,6 +126,12 @@ new_port_subscribe(void) } %} +%exception open_client { + $action + if (!result) { + SWIG_fail; + } +} snd_seq_t *open_client(const char *name, const char *type, int stream, int mode); snd_seq_port_subscribe_t *new_port_subscribe(); @@ -138,6 +143,12 @@ snd_seq_port_info_t *new_port_info(); snd_seq_client_info_t *new_client_info(); +%exception event_input { + $action + if (!result) { + SWIG_fail; + } +} snd_seq_event_t *event_input(snd_seq_t *handle); int snd_seq_control_queue_eventless(snd_seq_t *handle, int queue, int type, int value); int init_queue_tempo(snd_seq_t *handle, int queue, int bpm, int ppq); From c2bf1581a241bb411c35c9e15cd6da9c2645b361 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 22:40:55 +0300 Subject: [PATCH 25/28] Fix SWIG exception handling for all functions --- src/sequencer_alsa/sequencer_alsa.i | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/sequencer_alsa/sequencer_alsa.i b/src/sequencer_alsa/sequencer_alsa.i index 1a0fc5f..e38e89d 100644 --- a/src/sequencer_alsa/sequencer_alsa.i +++ b/src/sequencer_alsa/sequencer_alsa.i @@ -78,6 +78,7 @@ new_queue_status(snd_seq_t *handle, int queue) int err; err = snd_seq_queue_status_malloc(&qstatus); if (err < 0){ + PyErr_SetString(PyExc_IOError, snd_strerror(err)); return NULL; } return qstatus; @@ -96,6 +97,7 @@ new_client_info(void) int err; err = snd_seq_client_info_malloc(&cinfo); if (err < 0){ + PyErr_SetString(PyExc_IOError, snd_strerror(err)); return NULL; } return cinfo; @@ -108,6 +110,7 @@ new_port_info(void) int err; err = snd_seq_port_info_malloc(&pinfo); if (err < 0){ + PyErr_SetString(PyExc_IOError, snd_strerror(err)); return NULL; } return pinfo; @@ -120,39 +123,38 @@ new_port_subscribe(void) int err; err = snd_seq_port_subscribe_malloc(&subs); if (err < 0){ + PyErr_SetString(PyExc_IOError, snd_strerror(err)); return NULL; } return subs; } %} -%exception open_client { +%exception { $action if (!result) { SWIG_fail; } } + snd_seq_t *open_client(const char *name, const char *type, int stream, int mode); snd_seq_port_subscribe_t *new_port_subscribe(); snd_seq_queue_status_t *new_queue_status(snd_seq_t *handle, int queue); -void free_queue_status(snd_seq_queue_status_t *qstatus); snd_seq_port_info_t *new_port_info(); snd_seq_client_info_t *new_client_info(); -%exception event_input { - $action - if (!result) { - SWIG_fail; - } -} snd_seq_event_t *event_input(snd_seq_t *handle); +PyObject *client_poll_descriptors(snd_seq_t *handle); + +%exception; + +void free_queue_status(snd_seq_queue_status_t *qstatus); int snd_seq_control_queue_eventless(snd_seq_t *handle, int queue, int type, int value); int init_queue_tempo(snd_seq_t *handle, int queue, int bpm, int ppq); -PyObject *client_poll_descriptors(snd_seq_t *handle); %typemap(out) ssize_t { $result = PyInt_FromLong($1); } %typemap(in) ssize_t { $1 = PyInt_AsLong($input); } From d0d01c4f995b04710488ea9dfce62bbe1334c5ab Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 22:46:15 +0300 Subject: [PATCH 26/28] Check whether /dev/snd/seq is readable/writable --- tests/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.py b/tests/tests.py index 12a2b1f..1469b83 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -62,7 +62,7 @@ def get_writer_sequencer(self): return seq @unittest.skipIf(get_sequencer_type() != "alsa", "ALSA Sequencer not found, skipping test") - @unittest.skipIf(not os.path.exists("/dev/snd/seq"), "/dev/snd/seq is not available, skipping test") + @unittest.skipIf(not os.access("/dev/snd/seq", os.R_OK | os.W_OK), "/dev/snd/seq is not available, skipping test") def test_loopback_sequencer(self): rseq = self.get_reader_sequencer() wseq = self.get_writer_sequencer() From fbde41048050f780ba8801f7b65c4e0d5ec2c53d Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 22:59:19 +0300 Subject: [PATCH 27/28] Make event_input ignore EAGAIN errors When you're reading from a file descriptor in non-blocking mode and there's nothing to read, you get an EAGAIN error. We don't want to see those as exceptions; we want to see a None value returned by event_input(). --- src/sequencer_alsa/sequencer_alsa.i | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sequencer_alsa/sequencer_alsa.i b/src/sequencer_alsa/sequencer_alsa.i index e38e89d..4b5a97e 100644 --- a/src/sequencer_alsa/sequencer_alsa.i +++ b/src/sequencer_alsa/sequencer_alsa.i @@ -37,6 +37,11 @@ event_input(snd_seq_t *handle) int err; snd_seq_event_t *ev; err = snd_seq_event_input(handle, &ev); + if (err == -EAGAIN) + { + Py_INCREF(Py_None); + return Py_None; + } if (err < 0) { PyErr_SetString(PyExc_IOError, snd_strerror(err)); From fec1e8fdf4eae248612b9843a599956ebaccdb77 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Tue, 10 Oct 2017 23:09:33 +0300 Subject: [PATCH 28/28] Fix compiler warning about incompatible return types --- src/sequencer_alsa/sequencer_alsa.i | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/sequencer_alsa/sequencer_alsa.i b/src/sequencer_alsa/sequencer_alsa.i index 4b5a97e..9e2e357 100644 --- a/src/sequencer_alsa/sequencer_alsa.i +++ b/src/sequencer_alsa/sequencer_alsa.i @@ -40,7 +40,7 @@ event_input(snd_seq_t *handle) if (err == -EAGAIN) { Py_INCREF(Py_None); - return Py_None; + return (snd_seq_event_t*)Py_None; } if (err < 0) { @@ -152,9 +152,20 @@ snd_seq_port_info_t *new_port_info(); snd_seq_client_info_t *new_client_info(); -snd_seq_event_t *event_input(snd_seq_t *handle); PyObject *client_poll_descriptors(snd_seq_t *handle); +%exception event_input { + $action + if (result == (snd_seq_event_t*)Py_None) { + return Py_None; + } + if (!result) { + SWIG_fail; + } +} + +snd_seq_event_t *event_input(snd_seq_t *handle); + %exception; void free_queue_status(snd_seq_queue_status_t *qstatus);