class_docstr: Track the notes with an HMM based on a model of attack, decay, sustain,
  release (ADSR) envelopes.
  
  Parameters
  ----------
  onset_prob : float, optional
      Transition probability to enter an onset state.
  note_prob : float, optional
      Transition probability to enter a sounding note state.
  offset_prob : float, optional
      Transition probability to enter an offset state.
  attack_length : float, optional
      Minimum required attack (i.e. onset activation required) length.
  decay_length : float, optional
      Minimum required decay (i.e. note activation required) length.
  release_length : float, optional
      Minimum required release (i.e. note activation required) length.
  complete : bool, optional
      Require notes to transition all states (i.e. discard incomplete notes).
  onset_threshold : float, optional
      Require notes to have an onset activation greater or equal this
      threshold.
  note_threshold : float, optional
      Require notes to have a note activation greater equal this threshold.
  fps : float, optional
      Frames per second.
  pitch_offset : int, optional
      Pitch offset for the detected notes.
  
  References
  ----------
  
  .. [1] Rainer Kelz, Sebastian Böck and Gerhard Widmer,
         "Deep Polyphonic ADSR Piano Note Transcription",
         Proceedings of the 44th International Conference on Acoustics,
         Speech and Signal Processing (ICASSP), 2019.
  
  Examples
  --------
  Create a CNNPianoNoteProcessor and pass a file through the processor
  to obtain a note activation function (sampled with 50 frames per second).
  
  >>> proc = CNNPianoNoteProcessor()
  >>> act = proc('tests/data/audio/stereo_sample.wav')
  
  Track the notes by means on ADSR note tracking:
  >>> adsr = ADSRNoteTrackingProcessor()
  >>> adsr(act)  # doctest: +NORMALIZE_WHITESPACE
  array([[ 0.12, 72. , 1.44],
         [ 1.54, 41. , 1.84],
         [ 2.5 , 77. , 1.  ],
         [ 2.52, 65. , 0.96],
         [ 2.54, 60. , 0.82],
         [ 2.58, 56. , 0.82],
         [ 3.34, 75. , 0.82],
         [ 3.42, 43. , 0.74]])
docstr: Detect the notes in the given activation function.
  
  Parameters
  ----------
  activations : numpy array
      Combined note and onset activation function.
  
  Returns
  -------
  notes : numpy array
      Detected notes [seconds, pitches, duration].
docstr_tok: ["Detect","the","notes","in","the","given","activation","function",".","Parameters","--","--","--","--","--","activations",":","numpy","array","Combined","note","and","onset","activation","function",".","Returns","--","--","--","-","notes",":","numpy","array","Detected","notes","[","seconds",",","pitches",",","duration","]","."]
code: def process(self, activations, **kwargs):
      """
      Detect the notes in the given activation function.
  
      Parameters
      ----------
      activations : numpy array
          Combined note and onset activation function.
  
      Returns
      -------
      notes : numpy array
          Detected notes [seconds, pitches, duration].
  
      """
      notes = []
      note_path = np.arange(self.st.attack, self.st.release)
      # process each pitch individually
      for pitch in range(activations.shape[1]):
          # decode activations for this pitch with HMM
          with np.errstate(divide="ignore"):
              # ignore warnings when taking the log of 0
              path, _ = self.hmm.viterbi(activations[:, pitch, :])
          # extract HMM note segments
          segments = np.logical_and(
              path > self.st.attack, path < self.st.release
          )
          # extract start and end positions (transition points)
          idx = np.nonzero(np.diff(segments.astype(int)))[0]
          # add end if needed
          if len(idx) % 2 != 0:
              idx = np.append(idx, [len(activations)])
          # all sounding frames
          frames = activations[:, pitch, 0]
          # all frames with onset activations
          onsets = activations[:, pitch, 1]
          # iterate over all segments to decide which to keep
          for onset, offset in idx.reshape((-1, 2)):
              # extract note segment
              segment = path[onset:offset]
              # discard segment which do not contain the complete note path
              if (
                  self.complete
                  and np.setdiff1d(note_path, segment).any()
              ):
                  continue
              # discard segments without a real note
              if frames[onset:offset].max() < self.note_threshold:
                  continue
              # discard segments without a real onset
              if onsets[onset:offset].max() < self.onset_threshold:
                  continue
              # append segment as note
              notes.append(
                  [
                      onset / self.fps,
                      pitch + self.pitch_offset,
                      (offset - onset) / self.fps,
                  ]
              )
      # if no note notes are detected, return empty array
      if len(notes) == 0:
          return np.empty((0, 3))
      # sort the notes, convert timing information and return them
      return np.array(sorted(notes), ndmin=2)
  
imports: from __future__ import absolute_import, division, print_function
  import numpy
  from .onsets import OnsetPeakPickingProcessor, peak_picking
  from ..processors import ParallelProcessor, Processor, SequentialProcessor
  from ..utils import combine_events
label_desc: Use image node_id 2 for calling the ADSRNoteTrackingProcessor obj's underlying member method code with example usage: obj.process(activations) and returns: np, np