···11+MIT License
22+33+Copyright (c) 2025
44+55+Permission is hereby granted, free of charge, to any person obtaining a copy
66+of this software and associated documentation files (the "Software"), to deal
77+in the Software without restriction, including without limitation the rights
88+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+copies of the Software, and to permit persons to whom the Software is
1010+furnished to do so, subject to the following conditions:
1111+1212+The above copyright notice and this permission notice shall be included in all
1313+copies or substantial portions of the Software.
1414+1515+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+SOFTWARE.
+150
README.md
···11+# snmp-midi
22+33+Polls server metrics via SNMP and plays them as MIDI notes. Each stat maps to an instrument on its own channel — pitch tracks the value, velocity tracks intensity. The result is an ambient, real-time sonic portrait of a running server.
44+55+## Features
66+77+- Maps CPU, memory, disk, network, and process stats to MIDI instruments
88+- Per-core CPU load and multi-timescale load averages (1min / 5min / 15min)
99+- Configurable scales, octaves, note durations, and velocity ranges
1010+- Cascade stagger: voices roll in sequentially each poll cycle
1111+- Optional threshold triggers for alert notes
1212+- Works with hardware synths, virtual ports, or any MIDI-capable DAW
1313+- Roland GS reset on startup for hardware synth initialization
1414+1515+## Requirements
1616+1717+- Python 3.8+
1818+- SNMP-enabled server (`snmpd` with UCD-SNMP-MIB and HOST-RESOURCES-MIB)
1919+- MIDI output: hardware synth, software synth (FluidSynth), or DAW
2020+2121+## Installation
2222+2323+```bash
2424+python3 -m venv venv
2525+source venv/bin/activate
2626+pip install -r requirements.txt
2727+```
2828+2929+## Quick Start
3030+3131+```bash
3232+# Copy example config and edit with your server details
3333+cp config.example.yaml config.yaml
3434+$EDITOR config.yaml
3535+3636+# List available MIDI ports
3737+./main.py -l
3838+3939+# Run (port number from -l output)
4040+./main.py -P 0
4141+```
4242+4343+## Usage
4444+4545+```
4646+./main.py [options]
4747+4848+ -c FILE Config file (default: config.yaml)
4949+ -g Write default config to FILE and exit
5050+ -l List MIDI ports and exit
5151+ -H HOST SNMP host (overrides config)
5252+ -p PORT SNMP port (overrides config)
5353+ -C STR SNMP community string (overrides config)
5454+ -P NUM MIDI port by index number
5555+ -m NAME MIDI port by name
5656+ -v Use virtual MIDI port
5757+ -i SECS Poll interval in seconds (overrides config)
5858+```
5959+6060+## Configuration
6161+6262+Copy `config.example.yaml` to `config.yaml` and edit. The `config.yaml` file is gitignored so credentials stay local.
6363+6464+### Available stat types
6565+6666+| `stat_name` | `stat_key` | Description |
6767+|--------------------|---------------------------------------------|------------------------------------|
6868+| `cpu_load` | — | 1-minute load average |
6969+| `cpu_load_5min` | — | 5-minute load average |
7070+| `cpu_load_15min` | — | 15-minute load average |
7171+| `per_core_cpu_load`| `core0`, `core1`, … `coreN` | Per-core CPU % (hrProcessorLoad) |
7272+| `memory_usage` | `percent`, `total`, `available`, `cached` | Memory stats (UCD-SNMP-MIB) |
7373+| `disk_usage` | `percent`, `size`, `used` | First mounted filesystem |
7474+| `network_stats` | `in_bytes`, `out_bytes` | Cumulative octets (IF-MIB) |
7575+| `process_count` | — | hrSystemProcesses |
7676+7777+### Scales
7878+7979+`major` · `minor` · `pentatonic_major` · `pentatonic_minor` · `blues` · `dorian` · `mixolydian` · `chromatic`
8080+8181+### Mapping fields
8282+8383+```yaml
8484+mappings:
8585+ - stat_name: cpu_load # stat type (see table above)
8686+ stat_key: null # sub-key within stat, if applicable
8787+ channel: 0 # MIDI channel 0–15
8888+ program: 73 # GM program number 0–127
8989+ scale: pentatonic_major # musical scale
9090+ root: C # root note
9191+ octave: 5 # base octave
9292+ min_value: 0 # value mapped to lowest scale note
9393+ max_value: 10 # value mapped to highest scale note
9494+ note_duration: 1.0 # seconds (must be < poll_interval)
9595+ cc_number: null # also send value as CC, or null
9696+ enabled: true
9797+```
9898+9999+### Threshold triggers
100100+101101+```yaml
102102+triggers:
103103+ - stat_name: cpu_load
104104+ threshold: 8.0
105105+ above: true
106106+ note: 72
107107+ velocity: 127
108108+ channel: 0
109109+ duration: 0.5
110110+```
111111+112112+### Timing
113113+114114+`note_duration` must be less than `poll_interval` or note-off threads from earlier polls will cancel newer notes before slow-attack instruments sound. Use onboard reverb to blur the gaps between notes.
115115+116116+## Setting Up snmpd
117117+118118+```bash
119119+sudo apt-get install snmpd snmp-mibs-downloader
120120+```
121121+122122+`/etc/snmp/snmpd.conf` minimal additions:
123123+```
124124+rocommunity public 127.0.0.1
125125+rocommunity public 192.168.0.0/16
126126+```
127127+128128+```bash
129129+sudo systemctl restart snmpd
130130+# Test
131131+snmpwalk -v2c -c public localhost 1.3.6.1.2.1.1.1.0
132132+```
133133+134134+## Connecting a Synthesizer
135135+136136+**Hardware synth:** connect via USB-MIDI or DIN, then `./main.py -l` to find the port index.
137137+138138+**FluidSynth:**
139139+```bash
140140+sudo apt-get install fluidsynth fluid-soundfont-gm
141141+fluidsynth -a alsa -m alsa_seq /usr/share/sounds/sf2/FluidR3_GM.sf2 &
142142+./main.py -l
143143+./main.py -P <port number>
144144+```
145145+146146+**DAW:** create a virtual MIDI port in your DAW, then `./main.py -m "port name"`.
147147+148148+## License
149149+150150+[MIT](LICENSE)