code: def find_split_points(
      note_sequence, samples, sample_rate, min_length, max_length
  ):
      """Returns times at which there are no notes.
  
      The general strategy employed is to first check if there are places in the
      sustained pianoroll where no notes are active within the max_length window;
      if so the middle of the last gap is chosen as the split point.
  
      If not, then it checks if there are places in the pianoroll without sustain
      where no notes are active and then finds last zero crossing of the wav file
      and chooses that as the split point.
  
      If neither of those is true, then it chooses the last zero crossing within
      the max_length window as the split point.
  
      If there are no zero crossings in the entire window, then it basically gives
      up and advances time forward by max_length.
  
      Args:
          note_sequence: The NoteSequence to split.
          samples: The audio file as samples.
          sample_rate: The sample rate (samples/second) of the audio file.
          min_length: Minimum number of seconds in a split.
          max_length: Maximum number of seconds in a split.
  
      Returns:
          A list of split points in seconds from the beginning of the file.
      """
  
      if not note_sequence.notes:
          return []
  
      end_time = note_sequence.total_time
  
      note_sequence_sustain = (
          sequences_lib.apply_sustain_control_changes(note_sequence)
      )
  
      ranges_nosustain = find_inactive_ranges(note_sequence)
      ranges_sustain = find_inactive_ranges(note_sequence_sustain)
  
      nosustain_starts = [x[0] for x in ranges_nosustain]
      sustain_starts = [x[0] for x in ranges_sustain]
  
      nosustain_ends = [x[1] for x in ranges_nosustain]
      sustain_ends = [x[1] for x in ranges_sustain]
  
      split_points = [0.0]
  
      while end_time - split_points[-1] > max_length:
          max_advance = split_points[-1] + max_length
  
          # check for interval in sustained sequence
          pos = bisect.bisect_right(sustain_ends, max_advance)
          if (
              pos < len(sustain_starts)
              and max_advance > sustain_starts[pos]
          ):
              split_points.append(max_advance)
  
          # if no interval, or we didn't fit, try the unmodified sequence
          elif (
              pos == 0
              or sustain_starts[pos - 1]
              <= split_points[-1] + min_length
          ):
              # no splits available, use non sustain notes and find close zero crossing
              pos = bisect.bisect_right(nosustain_ends, max_advance)
  
              if (
                  pos < len(nosustain_starts)
                  and max_advance > nosustain_starts[pos]
              ):
                  # we fit, great, try to split at a zero crossing
                  zxc_start = nosustain_starts[pos]
                  zxc_end = max_advance
                  last_zero_xing = _last_zero_crossing(
                      samples,
                      int(math.floor(zxc_start * sample_rate)),
                      int(math.ceil(zxc_end * sample_rate)),
                  )
                  if last_zero_xing:
                      last_zero_xing = (
                          float(last_zero_xing) / sample_rate
                      )
                      split_points.append(last_zero_xing)
                  else:
                      # give up and just return where there are at least no notes
                      split_points.append(max_advance)
  
              else:
                  # there are no good places to cut, so just pick the last zero crossing
                  # check the entire valid range for zero crossings
                  start_sample = (
                      int(
                          math.ceil(
                              (split_points[-1] + min_length)
                              * sample_rate
                          )
                      )
                      + 1
                  )
                  end_sample = (
                      start_sample
                      + (max_length - min_length) * sample_rate
                  )
                  last_zero_xing = _last_zero_crossing(
                      samples, start_sample, end_sample
                  )
  
                  if last_zero_xing:
                      last_zero_xing = (
                          float(last_zero_xing) / sample_rate
                      )
                      split_points.append(last_zero_xing)
                  else:
                      # give up and advance by max amount
                      split_points.append(max_advance)
          else:
              # only advance as far as max_length
              new_time = min(
                  np.mean(ranges_sustain[pos - 1]), max_advance
              )
              split_points.append(new_time)
  
      if split_points[-1] != end_time:
          split_points.append(end_time)
  
      # ensure that we've generated a valid sequence of splits
      for prev, curr in zip(split_points[:-1], split_points[1:]):
          assert curr > prev
          assert curr - prev <= max_length + 1e-8
          if curr < end_time:
              assert curr - prev >= min_length - 1e-8
      assert end_time - split_points[-1] < max_length
  
      return split_points
  
file_path: magenta/magenta/models/onsets_frames_transcription/audio_label_data_utils.py
imports: from __future__ import absolute_import
  from __future__ import division
  from __future__ import print_function
  import bisect
  import math
  import librosa
  from note_seq import audio_io
  from note_seq import constants
  from note_seq import sequences_lib
  from note_seq.protobuf import music_pb2
  import numpy
  import tensorflow.compat.v1
label_desc: Use image node_id 4 for calling a global function with example usage: find_split_points(note_sequence, samples, sample_rate, min_length, max_length) and returns: split_points, list