# Use External MIDI
Connect your Quiver patches to MIDI keyboards, controllers, and DAWs.
## The AtomicF64 Bridge
MIDI events arrive on a separate thread. Use `AtomicF64` for thread-safe communication:
```rust,ignore
use std::sync::Arc;
use quiver::prelude::*;
// Create shared atomic values
let pitch = Arc::new(AtomicF64::new(0.0)); // V/Oct
let gate = Arc::new(AtomicF64::new(0.0)); // Gate signal
let mod_wheel = Arc::new(AtomicF64::new(0.0)); // CC1
// Clone for MIDI thread
let pitch_midi = Arc::clone(&pitch);
let gate_midi = Arc::clone(&gate);
```
## ExternalInput Module
Inject atomic values into your patch:
```rust,ignore
let pitch_in = patch.add("pitch", ExternalInput::voct(Arc::clone(&pitch)));
let gate_in = patch.add("gate", ExternalInput::gate(Arc::clone(&gate)));
patch.connect(pitch_in.out("out"), vco.in_("voct"))?;
patch.connect(gate_in.out("out"), env.in_("gate"))?;
```
ExternalInput variants:
| `::voct()` | V/Oct pitch | ±10V |
| `::gate()` | Gate | 0-5V |
| `::trigger()` | Trigger | 0-5V |
| `::cv()` | Unipolar CV | 0-10V |
| `::cv_bipolar()` | Bipolar CV | ±5V |
## MIDI to V/Oct Conversion
Standard conversion:
```rust,ignore
fn midi_note_to_voct(note: u8) -> f64 {
(note as f64 - 60.0) / 12.0
}
// In your MIDI handler:
pitch_midi.set(midi_note_to_voct(note_number));
```
| 36 (C2) | -2.0V |
| 48 (C3) | -1.0V |
| 60 (C4) | 0.0V |
| 72 (C5) | +1.0V |
## MidiState Helper
`MidiState` decodes raw MIDI bytes into a set of shared `Arc<AtomicF64>` control values —
`pitch`, `gate`, `velocity`, `mod_wheel`, `pitch_bend`, `aftertouch`, `sustain`, and
`expression` — that you can wire straight into `ExternalInput` modules.
```rust,ignore
let midi = MidiState::new();
// In your MIDI callback, feed raw MIDI bytes:
midi.handle_message(&[0x90, 60, 100]); // Note On, note 60, velocity 100
midi.handle_message(&[0x80, 60, 0]); // Note Off
midi.handle_message(&[0xB0, 1, 64]); // CC1 (mod wheel)
// Read the decoded control values on the audio thread:
let current_voct = midi.pitch.get();
let current_gate = midi.gate.get();
let mod_value = midi.mod_wheel.get();
// Or share the atomics directly with the patch:
let pitch_in = patch.add("pitch", ExternalInput::voct(Arc::clone(&midi.pitch)));
let gate_in = patch.add("gate", ExternalInput::gate(Arc::clone(&midi.gate)));
```
For a coherent (pitch, gate) pair that can never tear, prefer `midi.note_snapshot()`.
## Example: Complete MIDI Integration
```rust,ignore
{{#include ../../../examples/howto_midi.rs}}
```
## Gate vs Trigger
```mermaid
sequenceDiagram
participant K as Keyboard
participant G as Gate
participant T as Trigger
K->>G: Key Down
G->>G: Goes HIGH (+5V)
T->>T: Brief pulse (5ms)
Note over G: Stays HIGH while held
K->>G: Key Up
G->>G: Goes LOW (0V)
```
- **Gate**: Stays high while key held (for sustain)
- **Trigger**: Brief pulse at note start (for percussion)
## Velocity Mapping
Convert MIDI velocity (0-127) to CV:
```rust,ignore
fn velocity_to_cv(velocity: u8) -> f64 {
velocity as f64 / 127.0 * 10.0 // 0-10V range
}
```
Route to VCA for dynamics:
```rust,ignore
let velocity_in = patch.add("vel", ExternalInput::cv(vel_atomic));
patch.connect(velocity_in.out("out"), vca.in_("cv"))?;
```
## Pitch Bend
Pitch bend is typically ±2 semitones:
```rust,ignore
fn pitch_bend_to_voct(bend: i16) -> f64 {
// bend: -8192 to +8191
// Result: ±2 semitones = ±(2/12) V = ±0.167V
(bend as f64 / 8192.0) * (2.0 / 12.0)
}
```
Sum with note pitch:
```rust,ignore
let total_pitch = note_voct + bend_voct;
pitch_atomic.set(total_pitch);
```
## Thread Safety Notes
- `AtomicF64` uses relaxed ordering—fine for audio
- Updates are lock-free (no blocking)
- Read the latest value, never stale data