Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Elixir hot-reloading & MIDI events generation

Elixir hot-reloading & MIDI events generation

Lightning talk at ElixirConfEU 2017. Demonstrates how to use Elixir "hot-reloading" feature, together with MIDI events generation.

Video: https://www.youtube.com/watch?v=_VgcUatTilU&feature=youtu.be&t=2m2s
Repo: https://github.com/thbar/demo-elixir-reloading-music

Thibaut Barrère

May 04, 2017
Tweet

More Decks by Thibaut Barrère

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

  3. HOW TO MAKE IT INTERESTING?

    View Slide

  4. LIVE SOUND EVENTS GENERATION !

    View Slide

  5. HOW TO GENERATE ONE NOTE?

    View Slide

  6. RENOISE (MUSIC PRODUCTION SYSTEM)

    View Slide

  7. PORTMIDI (C LIBRARY)

    View Slide

  8. ELIXIR BINDINGS FOR PORTMIDI

    View Slide

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

    View Slide

  10. HOW TO BUILD A MUSIC LOOP?

    View Slide

  11. ▸ GenServer
    ▸ Process.send_after(xxx)

    View Slide

  12. 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

    View Slide

  13. 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

    View Slide

  14. 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

    View Slide

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

    View Slide

  16. 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

    View Slide

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

    View Slide

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

    View Slide

  19. DEMO
    Youtube Link

    View Slide