Slide 1

Slide 1 text

ELIXIR HOT-RELOADING & MIDI NOTES GENERATION https://github.com/thbar/demo-elixir-reloading-music

Slide 2

Slide 2 text

HOW TO IMPLEMENT A VERY SIMPLE ELIXIR CODE HOT-RELOADING EXAMPLE?

Slide 3

Slide 3 text

HOW TO MAKE IT INTERESTING?

Slide 4

Slide 4 text

LIVE SOUND EVENTS GENERATION !

Slide 5

Slide 5 text

HOW TO GENERATE ONE NOTE?

Slide 6

Slide 6 text

RENOISE (MUSIC PRODUCTION SYSTEM)

Slide 7

Slide 7 text

PORTMIDI (C LIBRARY)

Slide 8

Slide 8 text

ELIXIR BINDINGS FOR PORTMIDI

Slide 9

Slide 9 text

# 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})

Slide 10

Slide 10 text

HOW TO BUILD A MUSIC LOOP?

Slide 11

Slide 11 text

▸ GenServer ▸ Process.send_after(xxx)

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

I CAN HAZ RELOADING? Code.eval_file("music.exs")

Slide 16

Slide 16 text

HOW TO REACT TO FILE CHANGE? 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

Slide 17

Slide 17 text

AHA MOMENT GenServer reloading keeps the state across reloads. => We can keep the "current music tick" between reloads.

Slide 18

Slide 18 text

+-----------------+ +------------------------+ | (reloable) code | + | preserved state (tick) | +-----------------+ +------------------------+ | | \ / +---------------------+ +----------+ +---------+ | ex-portmidi process | -> | portmidi | -> | renoise | +---------------------+ +----------+ +---------+

Slide 19

Slide 19 text

DEMO Youtube Link