2 ["type"] = "EditorAction",
3 name = "Polyphonic Audio to MIDI",
5 author = "Ardour Team",
7 Analyze audio from the selected audio region to a selected MIDI region.
9 A MIDI region on the target track will have to be created first (use the pen tool).
11 This script uses the Polyphonic Transcription VAMP plugin from Queen Mary Univ, London.
12 The plugin works best at 44.1KHz input sample rate, and is tuned for piano and guitar music. Velocity is not estimated.
16 function factory () return function ()
17 local sel = Editor:get_selection ()
18 local sr = Session:nominal_sample_rate ()
19 local tm = Session:tempo_map ()
20 local vamp = ARDOUR.LuaAPI.Vamp ("libardourvampplugins:qm-transcription", sr)
21 local midi_region = nil
22 local audio_regions = {}
23 local start_time = Session:current_end_sample ()
24 local end_time = Session:current_start_sample ()
27 for r in sel.regions:regionlist ():iter () do
28 if r:to_midiregion():isnil() then
29 local st = r:position()
32 if st < start_time then
38 table.insert(audio_regions, r)
39 max_pos = max_pos + r:to_readable ():readable_length ()
41 midi_region = r:to_midiregion()
45 if #audio_regions == 0 then
46 LuaDialog.Message ("Polyphonic Audio to MIDI", "No source audio region(s) selected.\nAt least one audio-region to be analyzed need to be selected.", LuaDialog.MessageType.Error, LuaDialog.ButtonType.Close):run ()
49 if not midi_region then
50 LuaDialog.Message ("Polyphonic Audio to MIDI", "No target MIDI region selected.\nA MIDI region, ideally empty, and extending beyond the selected audio-region(s) needs to be selected.", LuaDialog.MessageType.Error, LuaDialog.ButtonType.Close):run ()
54 midi_region:set_initial_position(start_time)
55 midi_region:set_length(end_time - start_time, 0)
57 local pdialog = LuaDialog.ProgressWindow ("Audio to MIDI", true)
58 function progress (_, pos)
59 return pdialog:progress ((cur_pos + pos) / max_pos, "Analyzing")
62 for i,ar in pairs(audio_regions) do
63 local a_off = ar:position ()
64 local b_off = midi_region:quarter_note () - midi_region:start_beats ()
66 vamp:analyze (ar:to_readable (), 0, progress)
68 if pdialog:canceled () then
72 cur_pos = cur_pos + ar:to_readable ():readable_length ()
73 pdialog:progress (cur_pos / max_pos, "Generating MIDI")
75 local fl = vamp:plugin ():getRemainingFeatures ():at (0)
76 if fl and fl:size() > 0 then
77 local mm = midi_region:midi_source(0):model()
78 local midi_command = mm:new_note_diff_command ("Audio2Midi")
79 for f in fl:iter () do
80 local ft = Vamp.RealTime.realTime2Frame (f.timestamp, sr)
81 local fd = Vamp.RealTime.realTime2Frame (f.duration, sr)
82 local fn = f.values:at (0)
84 local bs = tm:exact_qn_at_sample (a_off + ft, 0)
85 local be = tm:exact_qn_at_sample (a_off + ft + fd, 0)
87 local pos = Evoral.Beats (bs - b_off)
88 local len = Evoral.Beats (be - bs)
89 local note = ARDOUR.LuaAPI.new_noteptr (1, pos, len, fn + 1, 0x7f)
90 midi_command:add (note)
92 mm:apply_command (Session, midi_command)
94 -- reset the plugin (prepare for next iteration)
105 function icon (params) return function (ctx, width, height, fg)
106 local txt = Cairo.PangoLayout (ctx, "ArdourMono ".. math.ceil(width * .7) .. "px")
107 txt:set_text ("\u{2669}") -- quarter note symbol UTF8
108 local tw, th = txt:get_pixel_size ()
109 ctx:set_source_rgba (ARDOUR.LuaAPI.color_to_rgba (fg))
110 ctx:move_to (.5 * (width - tw), .5 * (height - th))
111 txt:show_in_cairo_context (ctx)