Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
# Using Python
AMYboard runs MicroPython and gives you a full Python REPL for controlling the synthesizer, reading inputs, and building instruments and effects in real time.
## Connecting to the REPL
### From your computer
Connect AMYboard via USB-C and use a serial terminal or `mpremote`:
```bash
pip install mpremote
mpremote resume
```
You'll see the MicroPython `>>>` prompt. You can type Python commands directly.
### From the web
Open [amyboard.com](https://amyboard.com) -- it includes a Python REPL running in the browser. You can test code online before deploying it to hardware.
## Playing sounds with AMY
The `amy` module gives you direct access to the synthesizer engine:
```python
import amy
# Clear any existing setup
amy.reset()
# Play a 440Hz sine wave
amy.send(osc=0, wave=amy.SINE, freq=440, vel=1)
# Stop the note
amy.send(osc=0, vel=0)
```
### Using patches
AMY has 256+ built-in patches: 0-127 are Juno-6 analog, 128-255 are DX7 FM, 256 is piano, and 257+ are PCM drum kits.
In general, a patch will use multiple oscs. `synth` is our abstraction to manage multiple oscs at the same time.
```python
# Load Juno-6 patch #10 on synth 1
amy.send(synth=1, patch=10, num_voices=4)
# Play middle C
amy.send(synth=1, note=60, vel=1)
# Send the note-off
amy.send(synth=1, note=60, vel=0)
# Switch to DX7 patch #5
amy.send(synth=1, patch=133) # 128 + 5
```
### Polyphony with synths
The `synth` objects provide the specified level of polyphony. They also connect directly to MIDI channels:
```python
# Set up a 4-voice polyphonic Juno patch on synth 1 (MIDI channel 1).
amy.send(synth=1, patch=0, num_voices=4)
# Play notes on individual voices
amy.send(synth=1, note=60, vel=1) # C
amy.send(synth=1, note=64, vel=1) # E
amy.send(synth=1, note=67, vel=1) # G
# Send vel=0 with no note number for "all notes off"
amy.send(synth=1, vel=0)
# You can also play polyphonically with a MIDI keyboard (connected via the "MIDI in" jack) or your computer (writing to the AMYboard USB MIDI gadget) sending notes on channel 1.
```
## The `amyboard` module
The `amyboard` module provides functions specific to the AMYboard hardware:
```python
import amyboard
# CV I/O
amyboard.cv_out(5.0, channel=0) # Output 5V on CV out 1
volts = amyboard.cv_in(channel=0) # Read CV in 1
# Rotary encoders. The defaults are for the Adafruit single-encoder https://www.adafruit.com/product/5880
print(amyboard.read_encoder())
amyboard.init_buttons()
print(amyboard.read_buttons())
# OLED display (if connected)
amyboard.init_display()
amyboard.display.fill(0)
amyboard.display_refresh()
# Rotate the OLED (sh1107 only). One of 0, 90, 180, 270 degrees.
# The setting is saved and re-applied automatically on every boot.
amyboard.set_display_rotation(90)
print(amyboard.display_rotation()) # -> 90
# I2C
i2c = amyboard.get_i2c()
devices = i2c.scan()
print(devices)
# SD card
amyboard.mount_sd()
```
See the [Modular Synth Setup](modular.md) page for detailed CV and encoder examples.
## The sketch.py startup script
AMYboard automatically runs `sketch.py` from your current environment directory on boot. The top-level code runs once at startup, and if you define a `loop()` function it will be called repeatedly (~60ms interval). Use it to set up your default configuration:
```python
# /user/current/sketch.py
import amy, amyboard
# Set up my preferred patches
amy.send(synth=1, patch=0, num_voices=6) # Channel 1: Juno patch 0, 6-voice poly
amy.send(synth=10, num_voices=1, oscs_per_voice=1, synth_flags=3) # Channel 10: Drums (synth_flags magic).
# Set CV out 1 to 0V on startup
amyboard.cv_out(0.0, channel=0)
def loop():
pass
```
You can edit `sketch.py` directly on the hardware using the built-in text editor. Connect to your AMYboard with `screen` or `mpremote` (not `idf.py monitor`, which doesn't support full terminal emulation):
```bash
screen /dev/YOUR_SERIAL_PORT 115200
# or
mpremote connect /dev/YOUR_SERIAL_PORT
```
Then at the REPL:
```python
edit('current/sketch.py')
```
This opens a full-screen text editor. On macOS, use **Esc then the key** instead of Ctrl (macOS terminals intercept many Ctrl sequences):
- **Esc, S** — Save
- **Esc, Q** — Quit
- **Esc, X** — Cut line
- **Esc, V** — Paste
- **Esc, Z** — Undo
- **Esc, F** — Find
On Linux or Windows terminals, Ctrl-S, Ctrl-Q, etc. work as usual. See the [pye documentation](https://github.com/robert-hh/Micropython-Editor) for the full list of key bindings.
You can also create or edit files from [AMYboard Online](https://amyboard.com/editor) and send them to hardware with **Send to AMYboard**.
## File management
### On hardware
```python
from upysh import *
ls # List files
cat('sketch.py') # Print file contents
cd('/user') # Change directory
pwd # Print working directory
```
### Transfer files with mpremote
```bash
# Copy a file from your computer to AMYboard
mpremote resume fs cp my_script.py :my_script.py
# Copy a file from AMYboard to your computer
mpremote resume fs cp :sketch.py sketch.py
# Edit a file on AMYboard with your local editor
mpremote resume edit sketch.py
```
### Transfer files using the microSD card
First, copy the files you want to the card on your computer. Then insert it into the AMYboard and reboot (or try `amyboard.mount_sd()`). Then you can access those files directly from Python or copy them to user storage: `cp('/sd/file.wav', '/user/file.wav')`.
## Working with MIDI in Python
```python
import tulip
# Set up a MIDI callback
def my_midi_callback(is_sysex):
if is_sysex:
return # Ignore sysex
message = tulip.midi_in()
while message is not None and len(message) > 0:
# message is a bytes object
status = message[0]
if status & 0xF0 == 0x90: # Note on
note = message[1]
vel = message[2]
print(f"Note on: {note} velocity: {vel}")
# Maybe there are more messages queued?
message = tulip.midi_in()
tulip.midi_callback(my_midi_callback)
```
### Sending MIDI out
There are two ways to send MIDI out of AMYboard.
For one-off or arbitrary messages, use `tulip.midi_out()` with the raw MIDI bytes (a list, tuple or `bytes`). It sends immediately, out the MIDI out jack and the USB MIDI connection:
```python
import tulip
tulip.midi_out([0x90, 60, 127]) # note on, channel 1, note 60, velocity 127
tulip.midi_out([0x80, 60, 0]) # note off, channel 1, note 60
tulip.midi_out([0xB0, 74, 64]) # control change, channel 1, CC 74 = 64
```
For musically-timed output, let AMY send the MIDI for you. Set an oscillator's `wave` to `amy.AMY_MIDI`: instead of making a sound, that osc emits a MIDI note on/off whenever it receives a note. Because it's an ordinary osc, you can drive it from AMY's sequencer to get sample-accurate MIDI output that stays locked to AMY's tempo — for example, a pattern that plays an external synth:
```python
import amy
amy.send(osc=0, wave=amy.AMY_MIDI) # osc 0 now sends MIDI instead of audio
# Play a MIDI note on channel 1 every quarter note (48 ticks), held for an eighth note.
amy.send(osc=0, note=60, vel=1, sequence="0,48,1") # note on at tick 0 of each period
amy.send(osc=0, note=60, vel=0, sequence="24,48,2") # note off at tick 24 of each period
```
`amy.AMY_MIDI` always sends on MIDI channel 1, and notes that arrived over MIDI in are not echoed back out. See the [AMY MIDI docs](https://github.com/shorepine/amy/blob/main/docs/midi.md#sending-midi-out) for the full details.
### Syncing to MIDI clock
AMYboard's sequencer runs on its own internal clock by default. You can instead **follow** an external MIDI clock — slaving AMYboard to a DAW or drum machine — or **send** MIDI clock out to drive other gear from AMYboard.
**Following an external clock.** External realtime sync is off by default: incoming clock is ignored so AMYboard keeps its own tempo. Turn it on with `tulip.external_midi_sync()`:
```python
import tulip
tulip.external_midi_sync(True) # follow incoming MIDI clock
tulip.external_midi_sync(False) # back to AMYboard's internal clock (the default)
```
While following is on, realtime messages arriving on MIDI in (DIN jack or USB) drive the sequencer: `F8` (Timing Clock) sets the tempo and advances the sequencer in lock-step (24 clocks per quarter note), `FA` (Start) starts it, and `FC` (Stop) stops it. Start/Stop only take effect while sync is enabled, so a connected DAW can't start or stop your AMYboard pattern unless you've opted in. Turn sync back off to return to the internal clock.
**Sending clock.** There's no separate "send sync" switch — emit the realtime bytes yourself with `tulip.midi_out()` (out the MIDI out jack and USB, as above):
```python
tulip.midi_out([0xFA]) # Start
tulip.midi_out([0xF8]) # one Timing Clock tick — send 24 per quarter note
tulip.midi_out([0xFC]) # Stop
```
To send a steady clock locked to AMYboard's own tempo, drive `F8` from a sequencer callback. The sequencer runs at 48 ticks per quarter note (as above) and MIDI clock is 24, so send on every other tick (`period=2`):
```python
import tulip, sequencer
sequencer.tempo(120) # AMYboard's sequencer tempo, in BPM
def send_clock(tick):
tulip.midi_out([0xF8])
tulip.midi_out([0xFA]) # tell downstream gear to start
slot = tulip.seq_add_callback(send_clock, 0, 2) # fire every 2nd tick -> 24 / quarter
# ...later, to stop:
# tulip.midi_out([0xFC])
# tulip.seq_remove_callback(slot)
```
`tulip.seq_add_callback(fn, tick, period)` calls `fn(tick_count)` whenever `tick_count % period == tick`, and returns a slot id you pass to `tulip.seq_remove_callback()` to stop.
## Example: simple arpeggiator
```python
import amy, time
notes = [60, 64, 67, 72] # C major arpeggio
amy.send(synth=1, patch=0, num_voices=4) # Juno patch 0
for _ in range(10):
for note in notes:
amy.send(synth=1, note=note, vel=1)
time.sleep(0.15)
amy.send(synth=1, note=note, vel=0)
time.sleep(0.05)
```
**Note:** Keyboard interrupt (Ctrl-C) doesn't work while the loop is running, so beware of getting stuck. Pressing the "Reset" button will start you over.
## Example: CV-controlled pitch
```python
import amy, amyboard, time
amy.reset()
amy.send(osc=0, wave=amy.SINE, vel=1) # Sine wave, on
for _ in range(100):
v = amyboard.cv_in(channel=0)
# Map -10..+10V to 100..1000 Hz
freq = 100 + ((v + 10) / 20.0) * 900
amy.send(osc=0, freq=freq)
time.sleep(0.05)
```
## More resources
- [AMY wire protocol reference](https://github.com/shorepine/amy/blob/main/docs/api.md) -- every parameter you can control
- [AMY synth architecture](https://github.com/shorepine/amy/blob/main/docs/synth.md) -- deep dive into oscillators, filters, modulation
- [MicroPython documentation](https://docs.micropython.org/en/latest/) -- general MicroPython reference
[Back to Getting Started](README.md)
1a637d104This file is pinned to the selected release. Download the original to open it in your design tool.