'The main loop for a real-time call and response interaction.'

def run(self):

    start_time = time.time()
    self._captor = self._midi_hub.start_capture(self._qpm, start_time)
    if ((not self._clock_signal) and (self._metronome_channel is not None)):
        self._midi_hub.start_metronome(self._qpm, start_time, channel=self._metronome_channel)
    if (self._end_call_signal is not None):
        self._captor.register_callback(self._end_call_callback, signal=self._end_call_signal)
    if (self._panic_signal is not None):
        self._captor.register_callback(self._panic_callback, signal=self._panic_signal)
    if (self._mutate_signal is not None):
        self._captor.register_callback(self._mutate_callback, signal=self._mutate_signal)
    last_tick_time = time.time()
    listen_ticks = 0
    response_sequence = music_pb2.NoteSequence()
    response_start_time = 0
    response_duration = 0
    player = self._midi_hub.start_playback(response_sequence, allow_updates=True)
    for captured_sequence in self._captor.iterate(signal=self._clock_signal, period=self._tick_duration):
        if self._stop_signal.is_set():
            break
        if self._panic.is_set():
            response_sequence = music_pb2.NoteSequence()
            player.update_sequence(response_sequence)
            self._panic.clear()
        tick_time = captured_sequence.total_time
        if ((not self._clock_signal) and (self._metronome_channel is not None)):
            self._midi_hub.start_metronome(self._qpm, tick_time, channel=self._metronome_channel)
        captured_sequence.tempos[0].qpm = self._qpm
        tick_duration = (tick_time - last_tick_time)
        last_end_time = (max((note.end_time for note in captured_sequence.notes)) if captured_sequence.notes else 0.0)
        silent_tick = (last_end_time <= last_tick_time)
        if (not silent_tick):
            listen_ticks += 1
        if (not captured_sequence.notes):
            if (response_sequence.total_time <= tick_time):
                self._update_state(self.State.IDLE)
            if (self._captor.start_time < tick_time):
                self._captor.start_time = tick_time
            self._end_call.clear()
            listen_ticks = 0
        elif (self._end_call.is_set() or silent_tick or (listen_ticks >= self._max_listen_ticks)):
            if (listen_ticks < self._min_listen_ticks):
                tf.logging.info('Input    too    short    (%d    vs    %d).    Skipping.', listen_ticks, self._min_listen_ticks)
                self._captor.start_time = tick_time
            else:
                self._update_state(self.State.RESPONDING)
                capture_start_time = self._captor.start_time
                if silent_tick:
                    captured_sequence = adjust_sequence_times(captured_sequence, tick_duration)
                    captured_sequence.total_time = tick_time
                    capture_start_time += tick_duration
                num_ticks = self._midi_hub.control_value(self._response_ticks_control_number)
                if num_ticks:
                    response_duration = (num_ticks * tick_duration)
                else:
                    response_duration = (tick_time - capture_start_time)
                response_start_time = tick_time
                response_sequence = self._generate(captured_sequence, capture_start_time, response_start_time, (response_start_time + response_duration))
                if ((time.time() - response_start_time) >= (tick_duration / 4)):
                    push_ticks = (((time.time() - response_start_time) // tick_duration) + 1)
                    response_start_time += (push_ticks * tick_duration)
                    response_sequence = adjust_sequence_times(response_sequence, (push_ticks * tick_duration))
                    tf.logging.warn('Response    too    late.    Pushing    back    %d    ticks.', push_ticks)
                player.update_sequence(response_sequence, start_time=response_start_time)
                if self._allow_overlap:
                    self._captor.start_time = response_start_time
                else:
                    self._captor.start_time = (response_start_time + response_duration)
            self._end_call.clear()
            listen_ticks = 0
        else:
            self._update_state(self.State.LISTENING)
        if (self._mutate.is_set() and (not response_sequence.notes)):
            self._mutate.clear()
            tf.logging.warn('Ignoring    mutate    request    with    nothing    to    mutate.')
        if ((response_sequence.total_time <= tick_time) and (self._should_loop or self._mutate.is_set())):
            if self._mutate.is_set():
                new_start_time = (response_start_time + response_duration)
                new_end_time = (new_start_time + response_duration)
                response_sequence = self._generate(response_sequence, response_start_time, new_start_time, new_end_time)
                response_start_time = new_start_time
                self._mutate.clear()
            response_sequence = adjust_sequence_times(response_sequence, (tick_time - response_start_time))
            response_start_time = tick_time
            player.update_sequence(response_sequence, start_time=tick_time)
        last_tick_time = tick_time
    player.stop()
