https://github.com/thbar/demo-elixir-reloading-music
# Start a process for MIDI event queue
{:ok, pid} = PortMidi.open(:output, "Renoise MIDI-In")
note = 48 # C-4
velocity = 127
# Send "NOTE ON"
PortMidi.write(pid, {0x90, note, velocity})
# Send "NOTE OFF"
PortMidi.write(pid, {0x80, note})
GenServer
Process.send_after(xxx)
defmodule MidiPlayer do
use GenServer
def start_link do
{:ok, device} = PortMidi.open(:output, "Renoise MIDI-In")
tick_period = 50
Process.send_after(:midi, {:tick}, tick_period)
GenServer.start_link(__MODULE__, %{
current_tick: -1,
device: device,
tick_period: tick_period
}, name: :midi)
end
# SNIP
end
defmodule MidiPlayer do
def handle_info({:tick}, state) do
Process.send_after(:midi, {:tick}, state.tick_period)
current_tick = Map.fetch!(state, :current_tick) + 1
show_visual_feedback(current_tick)
play_notes(state.device, current_tick)
{:noreply, %{state | current_tick: current_tick}}
end
end
def play_notes(device, current_tick) do
notes = [0x54, 0x57, 0x5B, 0x60]
delay = 4
if rem(current_tick, delay) == 0 do
index = rem(div(current_tick, delay), Enum.count(notes))
note = Enum.at(notes, index)
PortMidi.write(device, {0x90, note, volume})
Process.send_after(:midi, {:note_off, note}, 50 * 2)
end
end
Code.eval_file("music.exs")
defmodule Monitor do
use ExFSWatch,
dirs: ["music.exs"],
listener_extra_args: "--latency=0.0"
def callback(_file_path, _events) do
Code.eval_file("music.exs")
end
end
Monitor.start
GenServer reloading keeps the state across reloads.
=> We can keep the "current music tick" between reloads.
+-----------------+ +------------------------+
| (reloable) code | + | preserved state (tick) |
+-----------------+ +------------------------+
| |
\ /
+---------------------+ +----------+ +---------+
| ex-portmidi process | -> | portmidi | -> | renoise |
+---------------------+ +----------+ +---------+