From d71214e20b824db5ebd8fbb87a112afe1c0a0a28 Mon Sep 17 00:00:00 2001 From: Brettles Date: Sat, 26 Aug 2023 07:06:11 +0000 Subject: [PATCH 01/17] Handy to know round-trip-time latency sometimes --- pymidi/protocol.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pymidi/protocol.py b/pymidi/protocol.py index 4b15ad4..b4766fd 100644 --- a/pymidi/protocol.py +++ b/pymidi/protocol.py @@ -169,3 +169,6 @@ def handle_timestamp(self, data, addr): elif packet.count == 2: offset_estimate = ((packet.timestamp_3 + packet.timestamp_1) / 2) - packet.timestamp_2 self.logger.debug('offset estimate: {}'.format(offset_estimate)) + + latency = (packet.timestamp_3-packet.timestamp_1)/10 + self.logger.info('Peer {} latency: {}ms'.format(self.peers_by_ssrc[packet.ssrc].name,latency)) From 3376855d5172e1ee3c30d7cef97170f897580496 Mon Sep 17 00:00:00 2001 From: Brettles Date: Sat, 26 Aug 2023 07:07:34 +0000 Subject: [PATCH 02/17] Commented out seemingly redundant code --- pymidi/client.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/pymidi/client.py b/pymidi/client.py index 677c4b2..b39f316 100644 --- a/pymidi/client.py +++ b/pymidi/client.py @@ -107,21 +107,22 @@ def _send_note(self, notestr, command, velocity=80, channel=1): self._send_rtp_command(command) def _send_rtp_command(self, command): - header = packets.MIDIPacketHeader.create( - rtp_header={ - 'flags': { - 'v': 0x2, - 'p': 0, - 'x': 0, - 'cc': 0, - 'm': 0x1, - 'pt': 0x61, - }, - 'sequence_number': ord('K'), - }, - timestamp=int(time.time()), - ssrc=self.ssrc, - ) +# Does note seem to be used anywhere - removing for possible performnace gain +# header = packets.MIDIPacketHeader.create( +# rtp_header={ +# 'flags': { +# 'v': 0x2, +# 'p': 0, +# 'x': 0, +# 'cc': 0, +# 'm': 0x1, +# 'pt': 0x61, +# }, +# 'sequence_number': ord('K'), +# }, +# timestamp=int(time.time()), +# ssrc=self.ssrc, +# ) packet = packets.MIDIPacket.create( header={ From 82cdee606acd3e0b81a86c8dfcf909a92c791dd1 Mon Sep 17 00:00:00 2001 From: Brettles Date: Sat, 26 Aug 2023 07:10:18 +0000 Subject: [PATCH 03/17] Even though the journal isn't decoded here it is handy to have available --- examples/example_server.py | 2 +- pymidi/server.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/example_server.py b/examples/example_server.py index 478299b..89330dd 100644 --- a/examples/example_server.py +++ b/examples/example_server.py @@ -59,7 +59,7 @@ def on_peer_connected(self, peer): def on_peer_disconnected(self, peer): self.logger.info('Peer disconnected: {}'.format(peer)) - def on_midi_commands(self, peer, command_list): + def on_midi_commands(self, peer, command_list, journal): for command in command_list: if command.command == 'note_on': key = command.params.key diff --git a/pymidi/server.py b/pymidi/server.py index e3fcb18..32901ab 100644 --- a/pymidi/server.py +++ b/pymidi/server.py @@ -25,7 +25,7 @@ def on_peer_connected(self, peer): def on_peer_disconnected(self, peer): pass - def on_midi_commands(self, peer, command_list): + def on_midi_commands(self, peer, command_list, journal): pass @@ -77,7 +77,7 @@ def _peer_disconnected_cb(self, peer): def _midi_command_cb(self, peer, midi_packet): commands = midi_packet.command.midi_list for handler in self.handlers: - handler.on_midi_commands(peer, commands) + handler.on_midi_commands(peer, commands, midi_packet.journal) def _build_control_protocol(self, host, port, family): logger.info('Control socket on {}:{}'.format(host, port)) From 71f2d6e9f014e4fa256e38a8c95ecd0605abca18 Mon Sep 17 00:00:00 2001 From: Brettles Date: Sat, 26 Aug 2023 07:11:12 +0000 Subject: [PATCH 04/17] Adding support for pitch bend decoding --- pymidi/packets.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pymidi/packets.py b/pymidi/packets.py index b01de85..c48c272 100644 --- a/pymidi/packets.py +++ b/pymidi/packets.py @@ -10,6 +10,7 @@ COMMAND_NOTE_ON = 0x90 COMMAND_AFTERTOUCH = 0xA0 COMMAND_CONTROL_MODE_CHANGE = 0xB0 +COMMAND_PITCH_BEND_CHANGE = 0xE0 def to_string(pkt): @@ -31,6 +32,10 @@ def to_string(pkt): items.append( '{} {} {}'.format(command, entry.params.controller, entry.params.value) ) + elif command == 'aftertouch': + items.append('{} {} {}'.format(command, entry.params.key, entry.params.touch)) + elif command == 'pitch_bend_change': + items.append('{} {} {}'.format(command, entry.params.lsb, entry.params.msb)) else: items.append(command) detail = ' '.join(('[{}]'.format(i) for i in items)) @@ -270,6 +275,7 @@ def create(self, **kwargs): note_off=COMMAND_NOTE_OFF, aftertouch=COMMAND_AFTERTOUCH, control_mode_change=COMMAND_CONTROL_MODE_CHANGE, + pitch_bend_change=COMMAND_PITCH_BEND_CHANGE ), ), 'channel' / If(_this.command_byte, Computed(_this.command_byte & 0x0F)), @@ -293,6 +299,10 @@ def create(self, **kwargs): 'controller' / Int8ub, 'value' / Int8ub, ), + 'pitch_bend_change': Struct( + 'lsb' / Int8ub, + 'msb' / Int8ub, + ), }, default=Struct( 'unknown' / GreedyBytes, From 456b74f97a7c2400cb1b93260b86f8773831a74d Mon Sep 17 00:00:00 2001 From: Brettles Date: Sat, 26 Aug 2023 07:13:13 +0000 Subject: [PATCH 05/17] Commented out seemingly redundant code --- pymidi/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymidi/client.py b/pymidi/client.py index b39f316..afb64ee 100644 --- a/pymidi/client.py +++ b/pymidi/client.py @@ -107,7 +107,7 @@ def _send_note(self, notestr, command, velocity=80, channel=1): self._send_rtp_command(command) def _send_rtp_command(self, command): -# Does note seem to be used anywhere - removing for possible performnace gain +# Does not seem to be used anywhere - removing for possible performnace gain # header = packets.MIDIPacketHeader.create( # rtp_header={ # 'flags': { From 49468143f1f5ce937c167da428a2a89309ba7669 Mon Sep 17 00:00:00 2001 From: Brettles Date: Sun, 27 Aug 2023 06:51:54 +0000 Subject: [PATCH 06/17] Deleting unused code --- pymidi/client.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/pymidi/client.py b/pymidi/client.py index afb64ee..f510edf 100644 --- a/pymidi/client.py +++ b/pymidi/client.py @@ -107,23 +107,6 @@ def _send_note(self, notestr, command, velocity=80, channel=1): self._send_rtp_command(command) def _send_rtp_command(self, command): -# Does not seem to be used anywhere - removing for possible performnace gain -# header = packets.MIDIPacketHeader.create( -# rtp_header={ -# 'flags': { -# 'v': 0x2, -# 'p': 0, -# 'x': 0, -# 'cc': 0, -# 'm': 0x1, -# 'pt': 0x61, -# }, -# 'sequence_number': ord('K'), -# }, -# timestamp=int(time.time()), -# ssrc=self.ssrc, -# ) - packet = packets.MIDIPacket.create( header={ 'rtp_header': { From 3c509eab6eb1bd7980a447716374c232c79e754b Mon Sep 17 00:00:00 2001 From: Brettles Date: Sun, 27 Aug 2023 07:20:50 +0000 Subject: [PATCH 07/17] Callback sends whole midi packet --- CHANGELOG.md | 3 +++ README.md | 4 ++-- examples/example_server.py | 4 ++-- pymidi/server.py | 5 ++--- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee083ee..88a093e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ * Bugfix: Fix crash when sender name contains non-ascii characters (#18). * Internal: Switched from `pipenv` to `poetry`. * Internal: Added `black` for code formatting. +* Improvement: Calculates latency to client and outputs as info. +* Imprvoement: Decodes pitch blend change messages. +* Breaking change: `on_midi_commands` callback handler now passes whole MIDI packet rather than a list of commands - this is useful if the journal (or other data) is required. ## v0.5.0 (2020-01-12) diff --git a/README.md b/README.md index 9c06f9c..c0f95c7 100644 --- a/README.md +++ b/README.md @@ -101,8 +101,8 @@ class MyHandler(server.Handler): def on_peer_disconnected(self, peer): print('Peer disconnected: {}'.format(peer)) - def on_midi_commands(self, peer, command_list): - for command in command_list: + def on_midi_commands(self, peer, midi_packet): + for command in midi_packet.command.midi_list: if command.command == 'note_on': key = command.params.key velocity = command.params.velocity diff --git a/examples/example_server.py b/examples/example_server.py index 89330dd..5acbf9a 100644 --- a/examples/example_server.py +++ b/examples/example_server.py @@ -59,8 +59,8 @@ def on_peer_connected(self, peer): def on_peer_disconnected(self, peer): self.logger.info('Peer disconnected: {}'.format(peer)) - def on_midi_commands(self, peer, command_list, journal): - for command in command_list: + def on_midi_commands(self, peer, midi_packet): + for command in midi_packet.command.midi_list: if command.command == 'note_on': key = command.params.key velocity = command.params.velocity diff --git a/pymidi/server.py b/pymidi/server.py index 32901ab..d65a950 100644 --- a/pymidi/server.py +++ b/pymidi/server.py @@ -25,7 +25,7 @@ def on_peer_connected(self, peer): def on_peer_disconnected(self, peer): pass - def on_midi_commands(self, peer, command_list, journal): + def on_midi_commands(self, peer, midi_packet): pass @@ -75,9 +75,8 @@ def _peer_disconnected_cb(self, peer): handler.on_peer_disconnected(peer) def _midi_command_cb(self, peer, midi_packet): - commands = midi_packet.command.midi_list for handler in self.handlers: - handler.on_midi_commands(peer, commands, midi_packet.journal) + handler.on_midi_commands(peer, midi_packet) def _build_control_protocol(self, host, port, family): logger.info('Control socket on {}:{}'.format(host, port)) From 7c0519ef8a1af435c7e5d52c00848ab5851078f0 Mon Sep 17 00:00:00 2001 From: Brettles Date: Sun, 27 Aug 2023 08:01:31 +0000 Subject: [PATCH 08/17] Minor fix to respect host variable during connect --- examples/example_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example_client.py b/examples/example_client.py index fd28479..187c417 100644 --- a/examples/example_client.py +++ b/examples/example_client.py @@ -48,7 +48,7 @@ def main(): host = '0.0.0.0' port = 5004 logger.info(f'Connecting to RTP-MIDI server @ {host}:{port} ...') - client.connect('0.0.0.0', port) + client.connect(host, port) logger.info('Connecting!') while True: logger.info('Striking key...') From 4c926671b184fa0e80b9445439fd65940adca93b Mon Sep 17 00:00:00 2001 From: Brettles Date: Sun, 27 Aug 2023 08:02:06 +0000 Subject: [PATCH 09/17] Allow source port specification and allocate sequential command/data ports --- pymidi/client.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/pymidi/client.py b/pymidi/client.py index f510edf..5ba0669 100644 --- a/pymidi/client.py +++ b/pymidi/client.py @@ -31,28 +31,33 @@ class AlreadyConnected(ClientError): class Client(object): - def __init__(self, name='PyMidi', ssrc=None): + def __init__(self, name='PyMidi', ssrc=None, sourcePort=None): """Creates a new Client instance.""" self.ssrc = ssrc or random.randint(0, 2 ** 32 - 1) - self.socket = None + self.socket = [None,None] # Need to have a command and data socket on the client side self.host = None self.port = None + self.sourcePort = sourcePort or 5004 + print(f'--sourcePort {self.sourcePort}') def connect(self, host, port): if self.host and self.port: raise ClientError(f'Already connected to {self.host}:{self.port}') - self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) pkt = packets.AppleMIDIExchangePacket.create( protocol_version=2, command=protocol.APPLEMIDI_COMMAND_INVITATION, initiator_token=random.randint(0, 2 ** 32 - 1), ssrc=self.ssrc, ) - for target_port in (port, port + 1): - logger.info(f'Sending exchange packet to port {target_port}...') - self.socket.sendto(pkt, (host, target_port)) - packet = self.get_next_packet() + + for index in (0, 1): + self.socket[index] = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.socket[index].bind(('0.0.0.0', self.sourcePort+index)) + + logger.info(f'Sending exchange packet to port {port+index} from port {self.sourcePort+index}...') + self.socket[index].sendto(pkt, (host, port+index)) + packet = self.get_next_packet(self.socket[index]) if not packet: raise Exception('No packet received') if packet._name != 'AppleMIDIExchangePacket': @@ -104,9 +109,9 @@ def _send_note(self, notestr, command, velocity=80, channel=1): } ], } - self._send_rtp_command(command) + self._send_rtp_command(self.socket[1], command) - def _send_rtp_command(self, command): + def _send_rtp_command(self, socket, command): packet = packets.MIDIPacket.create( header={ 'rtp_header': { @@ -127,10 +132,10 @@ def _send_rtp_command(self, command): journal='', ) - self.socket.sendto(packet, (self.host, self.port + 1)) + socket.sendto(packet, (self.host, self.port + 1)) - def get_next_packet(self): - data, addr = self.socket.recvfrom(1024) + def get_next_packet(self, socket): + data, addr = socket.recvfrom(1024) command = data[2:4] try: if data[0:2] == protocol.APPLEMIDI_PREAMBLE: From 4027a03caf59bf734a29be1e95b13eb5a205edb8 Mon Sep 17 00:00:00 2001 From: Brettles Date: Sun, 27 Aug 2023 08:14:54 +0000 Subject: [PATCH 10/17] Client name is sent to remote peer --- pymidi/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pymidi/client.py b/pymidi/client.py index 5ba0669..9b54d9d 100644 --- a/pymidi/client.py +++ b/pymidi/client.py @@ -38,7 +38,7 @@ def __init__(self, name='PyMidi', ssrc=None, sourcePort=None): self.host = None self.port = None self.sourcePort = sourcePort or 5004 - print(f'--sourcePort {self.sourcePort}') + self.name = name or 'PyMidi' def connect(self, host, port): if self.host and self.port: @@ -49,6 +49,7 @@ def connect(self, host, port): command=protocol.APPLEMIDI_COMMAND_INVITATION, initiator_token=random.randint(0, 2 ** 32 - 1), ssrc=self.ssrc, + name=self.name ) for index in (0, 1): From d7a899effd42f00547a6fc7c9347a98377dd2b5c Mon Sep 17 00:00:00 2001 From: Brettles Date: Sun, 27 Aug 2023 09:09:41 +0000 Subject: [PATCH 11/17] Embryonic handling for RS (journal sync) --- pymidi/protocol.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pymidi/protocol.py b/pymidi/protocol.py index b4766fd..f53f729 100644 --- a/pymidi/protocol.py +++ b/pymidi/protocol.py @@ -14,6 +14,7 @@ APPLEMIDI_COMMAND_INVITATION_ACCEPTED = b'OK' APPLEMIDI_COMMAND_INVITATION_REJECTED = b'NO' APPLEMIDI_COMMAND_TIMESTAMP_SYNC = b'CK' +APPLEMIDI_COMMAND_JOURNAL_SYNCHRONIZATION = b'RS' APPLEMIDI_COMMAND_EXIT = b'BY' @@ -105,6 +106,11 @@ def handle_command_message(self, command, data, addr): return peer = self._disconnect_peer(ssrc) self.logger.info('Peer {} exited'.format(peer)) + elif command == APPLEMIDI_COMMAND_JOURNAL_SYNCHRONIZATION: + # + # To be implemented + # + self.logger.warning('Ignoring unsupported command (journal sync): {}'.format(command)) else: self.logger.warning('Ignoring unrecognized command: {}'.format(command)) From c33d104ffd7713fc4c624bb96989a34521705a7c Mon Sep 17 00:00:00 2001 From: Brettles Date: Mon, 28 Aug 2023 09:22:40 +0000 Subject: [PATCH 12/17] 32 bit timestamp adherence --- pymidi/client.py | 7 +++---- pymidi/protocol.py | 6 ++---- pymidi/utils.py | 5 +++++ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pymidi/client.py b/pymidi/client.py index 9b54d9d..7281281 100644 --- a/pymidi/client.py +++ b/pymidi/client.py @@ -6,12 +6,12 @@ import socket import sys import random -import time from pymidi import packets from pymidi import protocol from pymidi import utils from pymidi.utils import b2h +from pymidi.utils import get_timestamp from construct import ConstructError try: @@ -69,13 +69,12 @@ def connect(self, host, port): self.port = port def sync_timestamps(self, port): - ts1 = int(time.time() * 1000) packet = packets.AppleMIDITimestampPacket.create( command=protocol.APPLEMIDI_COMMAND_TIMESTAMP_SYNC, ssrc=self.ssrc, count=count, padding=0, - timestamp_1=ts1, + timestamp_1=get_timestamp(), timestamp_2=0, timestamp_3=0, ) @@ -126,7 +125,7 @@ def _send_rtp_command(self, socket, command): }, 'sequence_number': ord('K'), }, - 'timestamp': int(time.time()), + 'timestamp': get_timestamp(), 'ssrc': self.ssrc, }, command=command, diff --git a/pymidi/protocol.py b/pymidi/protocol.py index f53f729..a9f44bb 100644 --- a/pymidi/protocol.py +++ b/pymidi/protocol.py @@ -1,9 +1,9 @@ import logging import random -import time from pymidi import packets from pymidi.utils import b2h +from pymidi.utils import get_timestamp from construct import ConstructError # Command messages are preceded with this sequence. @@ -114,7 +114,6 @@ def handle_command_message(self, command, data, addr): else: self.logger.warning('Ignoring unrecognized command: {}'.format(command)) - class ControlProtocol(BaseProtocol): def __init__(self, data_protocol=None, *args, **kwargs): super(ControlProtocol, self).__init__(*args, **kwargs) @@ -159,7 +158,6 @@ def handle_timestamp(self, data, addr): if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug(packet) - now = int(time.time() * 10000) # units of 100 microseconds if packet.count == 0: response = packets.AppleMIDITimestampPacket.build( dict( @@ -167,7 +165,7 @@ def handle_timestamp(self, data, addr): count=1, ssrc=self.ssrc, timestamp_1=packet.timestamp_1, - timestamp_2=now, + timestamp_2=get_timestamp(), timestamp_3=0, ) ) diff --git a/pymidi/utils.py b/pymidi/utils.py index d9a50b7..37a0fa4 100644 --- a/pymidi/utils.py +++ b/pymidi/utils.py @@ -1,5 +1,6 @@ import codecs import socket +import time from six import string_types from builtins import bytes @@ -51,3 +52,7 @@ def validate_addr(addr): raise ValueError('First param of address {} is not a valid ip'.format(repr(addr))) if not isinstance(addr[1], int): raise ValueError('Second param of address {} is not an int'.format(repr(addr))) + + +def get_timestamp(): + return int((time.time() * 10000) % 0x4000000) # RTP header timestamp is 32 bits From 11df0bed379f44abace91a6b599ae5293ac5836d Mon Sep 17 00:00:00 2001 From: Brettles Date: Mon, 28 Aug 2023 09:28:10 +0000 Subject: [PATCH 13/17] Send proper sequence number --- pymidi/client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pymidi/client.py b/pymidi/client.py index 7281281..0b44786 100644 --- a/pymidi/client.py +++ b/pymidi/client.py @@ -39,6 +39,7 @@ def __init__(self, name='PyMidi', ssrc=None, sourcePort=None): self.port = None self.sourcePort = sourcePort or 5004 self.name = name or 'PyMidi' + self.sequenceNumber = 1 def connect(self, host, port): if self.host and self.port: @@ -123,7 +124,7 @@ def _send_rtp_command(self, socket, command): 'm': 0x1, 'pt': 0x61, }, - 'sequence_number': ord('K'), + 'sequence_number': self.sequenceNumber, }, 'timestamp': get_timestamp(), 'ssrc': self.ssrc, @@ -133,6 +134,7 @@ def _send_rtp_command(self, socket, command): ) socket.sendto(packet, (self.host, self.port + 1)) + self.sequenceNumber += 1 def get_next_packet(self, socket): data, addr = socket.recvfrom(1024) From 6e3e7884b75e7ea78e9c850cedfffa555a28c784 Mon Sep 17 00:00:00 2001 From: Brettles Date: Mon, 28 Aug 2023 09:45:42 +0000 Subject: [PATCH 14/17] Send proper sequence number --- pymidi/packets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymidi/packets.py b/pymidi/packets.py index c48c272..badb0be 100644 --- a/pymidi/packets.py +++ b/pymidi/packets.py @@ -93,7 +93,7 @@ def create(self, **kwargs): RTPHeader = Struct( 'flags' / MIDIPacketHeaderFlags, - 'sequence_number' / Int16ub, # always 'K' + 'sequence_number' / Int16ub, ) MIDIPacketHeader = Struct( From 8b0920b37cf79065ac3938eb7572c5d84a37898a Mon Sep 17 00:00:00 2001 From: Brettles Date: Tue, 29 Aug 2023 02:37:05 +0000 Subject: [PATCH 15/17] Added disconnect() as per issue #28 --- pymidi/client.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pymidi/client.py b/pymidi/client.py index 0b44786..de53861 100644 --- a/pymidi/client.py +++ b/pymidi/client.py @@ -69,6 +69,25 @@ def connect(self, host, port): self.host = host self.port = port + def disconnect(self): + if not self.socket[0]: + raise ClientError(f'Not connected to anywhere') + + pkt = packets.AppleMIDIExchangePacket.create( + protocol_version=2, + command=protocol.APPLEMIDI_COMMAND_EXIT, + initiator_token=0, + ssrc=self.ssrc, + name=None + ) + self.socket[0].sendto(pkt, (self.host, self.port)) + + for index in (0, 1): self.socket[index].close() + + self.socket = [None,None] + self.host = None + self.port = None + def sync_timestamps(self, port): packet = packets.AppleMIDITimestampPacket.create( command=protocol.APPLEMIDI_COMMAND_TIMESTAMP_SYNC, From 5925c3c3803777593163e8c8fe3be7da1d7773d1 Mon Sep 17 00:00:00 2001 From: Brettles Date: Tue, 29 Aug 2023 23:55:51 +0000 Subject: [PATCH 16/17] Update commentary --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88a093e..2177ddd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,11 @@ * Internal: Switched from `pipenv` to `poetry`. * Internal: Added `black` for code formatting. * Improvement: Calculates latency to client and outputs as info. -* Imprvoement: Decodes pitch blend change messages. +* Improvement: Decodes pitch blend change messages. * Breaking change: `on_midi_commands` callback handler now passes whole MIDI packet rather than a list of commands - this is useful if the journal (or other data) is required. +* Improvement: RTP sequence numbers now increment +* Improvement: Timestamps don't overflow 32-bit field (issue #34) +* Improvement: disconnt() method for client (issue #28) ## v0.5.0 (2020-01-12) From f2cfa65d322845e4e2c4934b151d63622bab8e37 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 17 Oct 2023 03:12:52 +0000 Subject: [PATCH 17/17] Packet format for Receiver Feedback --- pymidi/packets.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pymidi/packets.py b/pymidi/packets.py index badb0be..3918c28 100644 --- a/pymidi/packets.py +++ b/pymidi/packets.py @@ -80,6 +80,13 @@ def create(self, **kwargs): 'timestamp_3' / Int64ub, ) +AppleMIDIReceiverFeedbackPacket = Struct( + '_name' / Computed('AppleMIDIReceiverFeedbackPacket'), + 'preamble' / Const(b'\xff\xffRS'), + 'ssrc' / Int32ub, + 'sequence_number' / Int32ub, +) + MIDIPacketHeaderFlags = Bitwise( Struct( 'v' / BitsInteger(2), # always 0x2