# Yet another MIDI recorder which dumps notes array readily playable by ticking

**URL:** <https://in-thread.sonic-pi.net/t/yet-another-midi-recorder-which-dumps-notes-array-readily-playable-by-ticking/7944>\
**Category:** Creations & Ideas\
**Created:** [July 5, 2023, 8:34am UTC](https://in-thread.sonic-pi.net/t/yet-another-midi-recorder-which-dumps-notes-array-readily-playable-by-ticking/7944 "2023-07-05T08:34:01Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![ocadaruma](https://dub1.discourse-cdn.com/flex017/user_avatar/in-thread.sonic-pi.net/ocadaruma/32/2564_2.png) [@ocadaruma](https://in-thread.sonic-pi.net/u/ocadaruma)\
**Post date:** [July 5, 2023, 8:34am UTC](https://in-thread.sonic-pi.net/t/yet-another-midi-recorder-which-dumps-notes-array-readily-playable-by-ticking/7944/1 "2023-07-05T08:34:01Z")

</div>

Though I know there are several attempts and approaches to record midi input as Sonic Pi code, I wrote yet another one to satisfy my use-case.

What I wanted:

- Play a phrase on MIDI device and dump it as Sonic Pi notes array, which are readily playable by ticking
  - With respecting timing by inserting rests appropriately

- Support recording chords

To achieve above, I needed to join ticks and played midi notes based on the timing.  
I ended up with accessing private variable of EventHistory class because other approaches (e.g. store event-history as an array in time-state) didn’t work as expected.

Tested on v4.4.0.

I hope it might be useful.

gist: [record\_midi.rb · GitHub](https://gist.github.com/ocadaruma/f5f5b17952683941f23a1ff420ffb873)

```ruby
configs = {
  bpm: 60,
  count_in: 4,
  beat_per_measure: 4,
  tick_per_beat: 4,
  midi_key: "/midi:microkey-25_keyboard:1/note_on",
}

define :note_to_sym do |n|
  info = note_info(n)
  "#{info.pitch_class}#{info.octave}".to_sym
end

define :get_events do |path|
  segments = path.delete_prefix("/").split("/")
  node = @event_history
           .instance_variable_get("@state")
           .instance_variable_get("@children")
  segments.each_with_index do |segment, i|
    n = node[segment]
    if n.nil?
      return []
    end
    if i < segments.size - 1
      node = n.instance_variable_get("@children")
    else
      return n.events
    end
  end
end

use_debug false
use_bpm configs[:bpm]
configs[:metronome_interval] = 1.0 / configs[:tick_per_beat]
configs[:tick_per_measure] = configs[:beat_per_measure] * configs[:tick_per_beat]

in_thread name: :metronome do
  use_real_time

  pattern = []
  configs[:tick_per_beat].times do |i|
    if i == 0
      pattern += [:hat_zap]
    else
      pattern += [:hat_bdu]
    end
  end

  configs[:count_in].times do
    sample :drum_cowbell
    sleep 1
  end

  loop do
    s = pattern.tick

    if look > 0 && look % configs[:tick_per_measure] == 0
      metronome_ticks = get_events "/metronome"
      notes = get_events configs[:midi_key]

      dump = []
      half = configs[:metronome_interval] / 2.0 / rt(1)

      metronome_ticks[0...configs[:tick_per_measure]].each do |t|
        lb, ub = t.time - half, t.time + half

        chord_notes = []
        # FIXME: Get rid of nested-loop
        notes.each do |n|
          if lb <= n.time && n.time < ub
            chord_notes << (note_to_sym n.val[0])
          end
        end
        dump << (chord_notes.any? ? chord_notes : nil)
      end

      puts dump.reverse
    end

    cue "/metronome"
    sample s
    sleep configs[:metronome_interval]
  end
end

in_thread name: :midi_listener do
  use_real_time
  loop do
    note, velocity = sync configs[:midi_key]
    synth :piano, note: note, amp: velocity / 127.0
  end
end

```
