Skip to content

Commit

Permalink
Add p600_recv and p600_send, rename p600_syxdump to p600_decode
Browse files Browse the repository at this point in the history
  • Loading branch information
jmechnich committed Oct 16, 2022
1 parent 3ff38a8 commit 7f8e27d
Show file tree
Hide file tree
Showing 11 changed files with 337 additions and 112 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ jobs:
- run: pip install --upgrade pip
- run: pip install "black<23" pylint==v3.0.0a3 mypy==v0.902
- run: black --diff --check .
- run: pylint --disable=all --enable=unused-import $(git ls-files '*.py' p600_syxdump)
- run: mypy --strict $(git ls-files '*.py' p600_syxdump)
- run: pylint --disable=all --enable=unused-import $(git ls-files '*.py' 'p600_*')
- run: mypy --strict --scripts-are-modules $(git ls-files '*.py' 'p600_*')
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ repos:
hooks:
- id: mypy
exclude: ^tests/
args: [--strict]
args: [--strict, --scripts-are-modules]
27 changes: 6 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ The following formats are supported:
and
[8](https://github.com/image-et-son/p600fw/blob/master/syxmgmt/storage_8.spec))

Currently, it is possible to print SysEx data in human readable form
using the script `p600_syxdump`.
The available executable programs are:

* `p600_decode` - decode patch data to human readable form
* `p600_recv` - receive patch data via MIDI
* `p600_send` - send patch and other SysEx data (i.e. firmware files) via MIDI

### Installation and usage

Expand All @@ -28,22 +31,4 @@ The easiest way is to install the software from https://pypi.org using `pip`:
pip install p600syx
```

After installation, the script can be used as follows:

```
usage: p600_syxdump [-h] [-d] [-m MESSAGE] [-p PROGRAM] [infile]
Print preset contents of Prophet-600 MIDI SysEx dumps.
positional arguments:
infile input sysex file. If no file is given, the script will read from
standard input.
optional arguments:
-h, --help show this help message and exit
-d, --debug turn on debug output
-m MESSAGE, --message MESSAGE
select message number (default: all messages)
-p PROGRAM, --program PROGRAM
select program number (default: all programs)
```
After installing the package, check `p600_SCRIPTNAME -h` for further usage instructions.
84 changes: 84 additions & 0 deletions p600_decode
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env python3

import argparse
import os
import sys

import p600syx

argparser = argparse.ArgumentParser(
description="Print preset contents of Prophet-600 MIDI SysEx dumps."
)
argparser.add_argument(
"-d", "--debug", action="store_true", help="turn on debug output"
)
argparser.add_argument(
"-m",
"--message",
type=int,
default=-1,
help="select message number (default: all messages)",
)
argparser.add_argument(
"-p",
"--program",
type=int,
default=-1,
help="select program number (default: all programs)",
)
argparser.add_argument(
"infile",
nargs="?",
help="input sysex file. If no file is given, the script will read from standard input.",
)
args = argparser.parse_args()

if args.infile:
if not os.path.exists(args.infile):
print(f"File {args.infile} not found, exiting")
sys.exit(1)
if args.debug:
print(f"Reading from file {args.infile}", file=sys.stderr)
with open(args.infile, "rb") as f:
raw_data = f.read()
else:
if args.debug:
print("Reading from stdin", file=sys.stderr)
raw_data = sys.stdin.buffer.read()

# Split SysEx stream at the terminating 0xf7, drop empty messages
msgs = [msg for msg in raw_data.split(b"\xf7") if len(msg)]
if args.debug:
print(f"Found {len(msgs)} messages")

for i, m in enumerate(msgs):
if args.message > -1 and i != args.message:
continue
parser = p600syx.factory.get_parser(m)
if not parser:
print(f"No suitable parser found for message {i}")
if args.debug:
for chunk in [m[i : i + 5] for i in range(0, len(m), 5)]:
for char in chunk:
print(f"{char:02x}", file=sys.stderr, end="")
print(" ", file=sys.stderr, end="")
print(file=sys.stderr)
continue
if args.debug:
print(f"Using {parser.name} for message {i}", file=sys.stderr)
program, parameters, data = parser.decode(m)
if args.program > -1 and program != args.program:
continue
print()
print(f'{"Program number":30}: {program:5}')
for name, value in parameters:
if name.startswith("Patch Name") and value > 0:
print(f"{name:30}: {value:5} {repr(chr(value))}")
else:
print(f"{name:30}: {value:5}")

if args.debug:
print(file=sys.stderr)
print(f"Data length: {len(data)}", file=sys.stderr)
print(data, file=sys.stderr)
print(file=sys.stderr)
105 changes: 105 additions & 0 deletions p600_recv
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/usr/bin/env python3

import argparse
import os
import sys
import time

import mido # type: ignore

from p600syx.util import SysEx, get_config, get_port

argparser = argparse.ArgumentParser(
description="Receive MIDI SysEx dumps from Prophet-600."
)
argparser.add_argument(
"-c",
"--config",
default=os.path.join(
os.environ["HOME"], ".config", f"{argparser.prog}.conf"
),
help="configuration file (default: %(default)s)",
)
argparser.add_argument(
"-d", "--debug", action="store_true", help="turn on debug output"
)
argparser.add_argument(
"-l", "--list", action="store_true", help="list MIDI ports and exit"
)
argparser.add_argument(
"-n",
"--number",
type=int,
default=-1,
help="select patch number (-1 for all, default: %(default)s)",
)
argparser.add_argument("-p", "--port", help="MIDI port that should be used")
argparser.add_argument(
"-s",
"--sleep",
type=int,
default=150,
help="number of milliseconds to wait between messages (default: %(default)s)",
)
argparser.add_argument(
"outfile",
nargs="?",
help="output sysex file",
)
args = argparser.parse_args()

inports = set(mido.get_input_names())
outports = set(mido.get_output_names())
if args.list:
print(f"MIDI in ports: {inports}")
print(f"MIDI out ports: {outports}")
sys.exit(0)

config = get_config(args, [args.config])
debug = config.get("debug", False)

port = config.get("port", "")
inport = get_port(inports, port)
outport = get_port(outports, port)

patch_number = int(config.get("number", -1))
if patch_number < 0:
patches = range(100)
else:
patches = range(patch_number, patch_number + 1)

if debug:
print(f"Dumping {len(patches)} patches", file=sys.stderr)

data = b""
with mido.open_output(outport) as midiout:
with mido.open_input(inport) as midiin:
for i, patch in enumerate(patches):
if debug:
print(f"Requesting patch {i:02}", file=sys.stderr)
msg = mido.Message(
"sysex",
data=[
SysEx.SYSEX_ID_0,
SysEx.SYSEX_ID_1,
SysEx.SYSEX_ID_2,
SysEx.SYSEX_COMMAND_PATCH_DUMP_REQUEST,
patch,
],
)
midiout.send(msg)

reply = midiin.receive()
data += bytes(reply.bytes())
time.sleep(args.sleep / 1000.0)

outfile = config.get("outfile", None)
if outfile:
if debug:
print(f"Writing to file {outfile}", file=sys.stderr)
with open(outfile, "wb") as f:
f.write(data)
else:
if debug:
print(f"Using standard output", file=sys.stderr)
sys.stdout.buffer.write(data)
73 changes: 73 additions & 0 deletions p600_send
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env python3

import argparse
import os
import sys
import time

import mido # type: ignore

from p600syx.util import get_config, get_port

argparser = argparse.ArgumentParser(
description="Send MIDI SysEx dumps to Prophet-600."
)
argparser.add_argument(
"-c",
"--config",
default=os.path.join(
os.environ["HOME"], ".config", f"{argparser.prog}.conf"
),
help="configuration file (default: %(default)s)",
)
argparser.add_argument(
"-d", "--debug", action="store_true", help="turn on debug output"
)
argparser.add_argument(
"-l", "--list", action="store_true", help="list MIDI ports and exit"
)
argparser.add_argument("-p", "--port", help="MIDI port that should be used")
argparser.add_argument(
"-s",
"--sleep",
type=int,
default=150,
help="number of milliseconds to wait between messages (default: %(default)s)",
)
argparser.add_argument(
"infile",
nargs="?",
help="input sysex file",
)
args = argparser.parse_args()

outs = set(mido.get_output_names())
if args.list:
print(f"MIDI ports: {outs}")
sys.exit(0)

config = get_config(args, [args.config])
debug = config.get("debug", False)

port = config.get("port", "")
outport = get_port(outs, port)

infile = config.get("infile", None)
if not infile:
print(f"Requiring input file, exiting")
sys.exit(1)
if not os.path.exists(infile):
print(f"File {infile} not found, exiting")
sys.exit(1)
if debug:
print(f"Reading from file {infile}", file=sys.stderr)
messages = mido.read_syx_file(infile)
if debug:
print(f"Read {len(messages)} from file, sending now", file=sys.stderr)

with mido.open_output(outport) as o:
for i, m in enumerate(messages):
if debug:
print(f"Sending message {i+1}", file=sys.stderr)
o.send(m)
time.sleep(args.sleep / 1000.0)
Loading

0 comments on commit 7f8e27d

Please sign in to comment.