"""Send random chords of MIDI note events to a synth, and record audio.

By default, prints the number of JACK xruns (buffer overruns or underruns)
produced during the MIDI playback and capture process.
"""

import numpy as np
import muser.iodata as iodata
import muser.sequencer as sequencer
import muser.utils as utils
import scipy.io.wavfile
import os
import time

import cProfile
import pstats

# User and synth parameters
data_dir = '/tmp/muser/'
os.makedirs(data_dir, exist_ok=True)
synth_outports = ["Pianoteq55:out_1", "Pianoteq55:out_2"]
synth_midi_in = "Pianoteq55:midi_in"

# Batch generation parameters
chord_size = 1
batch_size = 2
batches = 1
print_details = True
profile_capture = True

# data structure
chord_dtype = np.dtype([('velocity_vector', np.uint8, iodata.N_PITCHES),
                        ('captured_buffers', object)])
chord_batches = np.ndarray([batches, batch_size], dtype=chord_dtype)

# generate chord vectors
chord_gen = sequencer.random_pitch_vector
chord_batches['velocity_vector'] = utils.get_batches(chord_gen, batches,
                                                     batch_size, [chord_size])

# JACK client initialization
channels = len(synth_outports)
jack_client = iodata.SynthInterfaceClient(inports=channels)
samplerate = jack_client.samplerate

jack_client.activate()
try:
    # connect MIDI and audio ports of synthesizer and JACK client
    jack_client.disconnect_all()
    jack_client.connect(jack_client.midi_outport, synth_midi_in)
    for port_pair in zip(synth_outports, jack_client.inports):
        port_pair[1].disconnect()
        jack_client.connect(*port_pair)

    start_time = time.time()
    for batch in chord_batches:
        for chord in batch:
            velocity_vector = chord['velocity_vector']
            notes_on = iodata.vector_to_midi_events('ON', velocity_vector,
                                                    velocity=100)
            notes_off = iodata.vector_to_midi_events('OFF', velocity_vector)
            events_sequence = [notes_on, notes_off]
            if profile_capture:
                cProfile.run('jack_client.capture_events(events_sequence, '
                             'blocks=(250, 25), init_blocks=25, '
                             'amp_testrate=50)', 'capture_events-profile')
            else:
                jack_client.capture_events(events_sequence, blocks=(250, 25),
                                           init_blocks=25)

            chord['captured_buffers'] = jack_client.drop_captured()

    if profile_capture:
        profile = pstats.Stats('capture_events-profile').strip_dirs()
        profile.strip_dirs().sort_stats('time').print_stats(10)

    if print_details:
        print("{} Xruns".format(jack_client.n_xruns))
        for xrun in (jack_client.xruns - start_time):
            print('{:.4f} s'.format(xrun[0]))
        print("\nCapture timepoints")
        print('{:>10}  \t{:>10}'.format('Start', 'Stop'))
        for item in jack_client.captured_sequences:
            times = np.array(item[1]) - start_time
            xrun = " (Xrun)" if item[0] is None else ''
            print("{:10.4f} s\t{:10.4f} s {}".format(times[0], times[1], xrun))

except (KeyboardInterrupt, SystemExit):
    print('\nUser or system interrupt, dismantling JACK clients!')
    jack_client.dismantle()
    raise

jack_client.dismantle()

# store chord batches
batches_dir = os.path.join(data_dir, 'chord_batches')
os.makedirs(batches_dir, exist_ok=True)
pickle_path = os.path.join(batches_dir, 'chord_batches.pickle')
chord_batches.dump(pickle_path)

for b, batch in enumerate(chord_batches):
    for c, chord in enumerate(batch):
        snd = iodata.buffers_to_snd(chord['captured_buffers'])
        wavfile_name = 'batch{}_chord{}.wav'.format(b, c)
        wavfile_path = os.path.join(batches_dir, wavfile_name)
        scipy.io.wavfile.write(wavfile_path, samplerate, snd)

@@ -1381,16 +1381,83 @@
  client%0A
+    # (disconnect all first to prevent errors if auto-reconnected)%0A
     jack
@@ -1613,42 +1613,8 @@
 s):%0A
-        port_pair%5B1%5D.disconnect()%0A
     
@@ -2080,56 +2080,23 @@
     
-if profile_capture:%0A                cProfile.run
+capture_exec = 
 ('ja
@@ -2167,17 +2167,16 @@
         
- 
 'blocks=
@@ -2201,16 +2201,33 @@
 ocks=25,
+ amp_testrate=50,
  '%0A     
@@ -2253,154 +2253,125 @@
     
- 'amp_testrate=50)', 'capture_events-profile')%0A            else:%0A                jack_client.capture_events(events_sequence, blocks=(250, 25),%0A   
+'max_xruns=1)')%0A            if profile_capture:%0A                cProfile.run(capture_exec, 'capture_events-profile')%0A
     
@@ -2370,32 +2370,38 @@
 e')%0A            
+else:%0A
                 
@@ -2404,36 +2404,26 @@
     
-            init_blocks=25)%0A
+exec(capture_exec)
 %0A   

Call to ``capture_events`` defined once as a string so no redundancy needed regardless of profiling. And did not need to individually disconnect ``port_pairs`` now that ``disconnect_all`` is called before.