docs: def search_gen(self, query, audio_basename=None, case_sensitive=False,
                     subsequence=False, supersequence=False, timing_error=0.0,
                     anagram=False, missing_word_tolerance=0):
          """
          A generator that searches for the `query` within the audiofiles of the
          src_dir.
  
          Parameters
          ----------
          query : str
              A string that'll be searched. It'll be splitted on spaces and then
              each word gets sequentially searched.
          audio_basename : str, optional
              Search only within the given audio_basename.
  
              Default is `None`
          case_sensitive : bool, optional
              Default is `False`
          subsequence : bool, optional
              `True` if it's not needed for the exact word be detected and larger
              strings that contain the given one are fine.
  
              If the query is a sentences with multiple words, it'll be
              considered for each word, not the whole sentence.
  
              Default is `False`.
          supersequence : bool, optional
              `True` if it's not needed for the exact word be detected and
              smaller strings that are contained within the given one are fine.
  
              If the query is a sentences with multiple words, it'll be
              considered for each word, not the whole sentence.
  
              Default is `False`.
          anagram : bool, optional
              `True` if it's acceptable for a complete permutation of the word to
              be found. e.g. "abcde" would be acceptable for "edbac".
  
              If the query is a sentences with multiple words, it'll be
              considered for each word, not the whole sentence.
  
              Default is `False`.
          timing_error : None or float, optional
              Sometimes other words (almost always very small) would be detected
              between the words of the `query`. This parameter defines the
              timing difference/tolerance of the search.
  
              Default is 0.0 i.e. No timing error is tolerated.
          missing_word_tolerance : int, optional
              The number of words that can be missed within the result.
              For example, if the query is "Some random text" and the tolerance
              value is `1`, then "Some text" would be a valid response.
              Note that the first and last words cannot be missed. Also,
              there'll be an error if the value is more than the number of
              available words. For the example above, any value more than 1
              would have given an error (since there's only one word i.e.
              "random" that can be missed)
  
              Default is 0.
  
          Yields
          ------
          {"File Name": str, "Query": `query`, "Result": (float, float)}
              The result of the search is returned as a tuple which is the value
              of the "Result" key. The first element of the tuple is the
              starting second of `query` and the last element is the ending
              second of `query`
  
          Raises
          ------
          AssertionError
              If `missing_word_tolerance` value is more than the total number of
              words in the query minus 2 (since the first and the last word
              cannot be removed)
          """
          def case_sensitivity_handler(case_sensitive=case_sensitive):
  
              def get_query_words(query, case_sensitive=case_sensitive):
                  query_words = list(
                      filter(None, ''.join(
                          filter(lambda char: char in (ascii_letters + " "),
                                 list(query))).split(" ")))
                  if case_sensitive:
                      return query_words
                  return [q.lower() for q in query_words]
  
              def get_timestamps(case_sensitive=case_sensitive):
                  timestamps = self.get_timestamps().copy()
                  if not case_sensitive:
                      return {
                          audio_basename: [
                              _WordBlock(word=word_block.word.lower(),
                                         start=word_block.start,
                                         end=word_block.end)
                              for word_block in timestamps[audio_basename]]
                          for audio_basename in timestamps}
                  return timestamps
  
              return locals()
  
          query_words = case_sensitivity_handler()["get_query_words"](query)
          timestamps = case_sensitivity_handler()["get_timestamps"]()
  
          assert abs(missing_word_tolerance -
                     (len(query_words) - 2)) >= 0, (
              "The number of words that can be missing must be less than "
              "the total number of words within the query minus the first and "
              "the last word."
          )
  
          for audio_filename in (
                  (lambda: (timestamps.keys() if audio_basename is None else
                            [audio_basename]))()):
              result = list()
              missed_words_so_far = 0
              query_cursor = 0
              try:
                  for word_block in timestamps[audio_filename]:
                      if (
                              # When the query is identical
                              (word_block.word == query_words[query_cursor]) or
                              # When the query is a subsequence of what's
                              # available
                              (subsequence and
                               self._is_subsequence_of(query_words[query_cursor],
                                                       word_block.word)) or
                              # When the query is a supersequence of what's
                              # available
                              (supersequence and self._is_supersequence_of(
                                  query_words[query_cursor], word_block.word)) or
                              # When query is a permutation of what's available.
                              (anagram and self._is_anagram_of(
                                  query_words[query_cursor], word_block.word))
                      ):
                          result.append(word_block)
  
                          if timing_error is not None:
                              try:
                                  if round(result[-1].start -
                                           result[-2].end, 4) > timing_error:
                                      result = list()
                                      query_cursor = 0
                              except IndexError:
                                  pass
  
                          if self._partial_search_validator(
                                  query_words, [x.word for x in result],
                                  anagram=anagram,
                                  subsequence=subsequence,
                                  supersequence=supersequence):
                              yield {
                                  "File Name": audio_filename,
                                  "Query": query,
                                  "Result": tuple([result[0].start,
                                                   result[-1].end])}
                              result = list()
                              query_cursor = 0
  
                          else:
                              query_cursor += 1
  
                      elif missed_words_so_far > missing_word_tolerance:
                          result = list()
                          query_cursor = 0
  
                      elif (missing_word_tolerance > 0) and (len(result) > 0):
                          result.append(word_block)
                          missed_words_so_far += 1
  
              except KeyError:
                  # This is needed for the case where no timestamp is present.
                  pass
  
              except IndexError:
                  # This is needed when multiple timestamps are present, and
                  # advanced control structures like `missed_word_tolerance` are
                  # non-zero. In that case, it can search to the end of the first
                  # timestamp looking to complete its partial result and since
                  # there are no more `word_block`s left, it returns an error.
                  # `continue` should be used to reset the partial result and
                  # move to the next timestamp.
                  continue