# NetMD
Drive Sony MiniDisc recorders over NetMD USB from Elixir. A port of
[netmd-js](https://github.com/cybercase/netmd-js) (the library behind
Web MiniDisc) cross-referenced against libnetmd from
[linux-minidisc](https://github.com/linux-minidisc/linux-minidisc),
running on [bodge_usb](https://github.com/lawik/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/1` keeps 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
```elixir
# 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:
```elixir
{: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`](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
```elixir
def deps do
[
{:netmd, github: "lawik/netmd"}
]
end
```
## License
GPL-2.0, matching the reference implementations this library ports.