Drive Sony MiniDisc recorders over NetMD USB from Elixir. A port of netmd-js (the library behind Web MiniDisc) cross-referenced against libnetmd from linux-minidisc, running on bodge_usb. Linux only, no native code beyond the bodge_usb NIF.
Implemented mostly using LLM. But proven on hardware and made with some care. A port of the awesome C and JS/TS work that's been done already.
What works
- Enumerate and open the ~48 known NetMD devices
- Disc listing: titles (half and full width Shift-JIS), groups, per-track encoding, duration and protection, capacity
- Playback control, seeking, eject
- Renaming discs and tracks, erasing and moving tracks
- Track download (recording to disc) through the secure session, including the open-source EKB and DES retail MAC key negotiation
- Track upload from an MZ-RH1, with AEA/WAV headers ready for ffmpeg
- Factory mode: direct RAM/EEPROM/peripheral access, firmware patching,
raw UTOC sector read/write and the display override (dangerous; see
NetMD.Factory) - Survive the USB re-enumeration that inserting or ejecting a disc (or a
session reset) triggers:
NetMD.open/1keeps the same device handle across it by default, reconnecting transparently (NetMD.Transport.Managed)
Tested hardware
Confirmed working on a Sony MZ-N707. The other ~48 devices in the table are wired up by USB id and protocol but have not each been exercised on hardware.
Usage
# See every connected NetMD before picking one to open.
for d <- NetMD.list_devices(), do: IO.puts("#{d.name} on bus #{d.bus}/#{d.address}")
{:ok, device} = NetMD.open()
{:ok, disc} = NetMD.list_content(device)
IO.puts("#{disc.title}: #{disc.track_count} tracks")
:ok = NetMD.play(device)
:ok = NetMD.rename_disc(device, "Mix Tape")
# Download audio: raw PCM (16-bit big-endian stereo, 44100 Hz) or
# pre-encoded ATRAC3 for the LP modes.
track = %NetMD.Track{title: "New Song", format: :lp2, data: atrac3_data}
{:ok, %{track: n}} = NetMD.download(device, track)
NetMD.close(device)The facade delegates to layers that are usable on their own; see the
module docs of NetMD.Commands, NetMD.Interface, NetMD.Session,
NetMD.Device, NetMD.Query and NetMD.Factory.
Running without hardware
NetMD.Simulator is a virtual NetMD device that decodes the protocol and
holds disc state, so the whole library runs in-process with no USB:
{:ok, device} = NetMD.open(transport: NetMD.Simulator)
{:ok, disc} = NetMD.list_content(device)To exercise the real usbfs transport too, NetMD.Simulator.Gadget
presents that same brain as an actual USB device over FunctionFS; with
dummy_hcd it and a host driving NetMD.Transport.Usb run in one VM.
See vm/README.md.
Permissions
Accessing /dev/bus/usb needs root or a udev rule for your user, e.g.
SUBSYSTEM=="usb", ATTRS{idVendor}=="054c", MODE="0666"Testing
The protocol layers are verified byte-for-byte against golden vectors
generated from netmd-js itself, and the full flows replay against a
scripted mock transport. See TESTING.md.
Installation
def deps do
[
{:netmd, github: "lawik/netmd"}
]
endLicense
GPL-2.0, matching the reference implementations this library ports.