class_docstr: Audio samples IO handling within a given dora xp.
  
  The sample manager handles the dumping and loading logic for generated and
  references samples across epochs for a given xp, providing a simple API to
  store, retrieve and compare audio samples.
  
  Args:
      xp (dora.XP): Dora experiment object. The XP contains information on the XP folder
          where all outputs are stored and the configuration of the experiment,
          which is useful to retrieve audio-related parameters.
      map_reference_to_sample_id (bool): Whether to use the sample_id for all reference samples
          instead of generating a dedicated hash id. This is useful to allow easier comparison
          with ground truth sample from the files directly without having to read the JSON metadata
          to do the mapping (at the cost of potentially dumping duplicate prompts/references
          depending on the task).
docstr: Adds a single sample.
  The sample is stored in the XP's sample output directory, under a corresponding epoch folder.
  Each sample is assigned an id which is computed using the input data. In addition to the
  sample itself, a json file containing associated metadata is stored next to it.
  
  Args:
      sample_wav (torch.Tensor): sample audio to store. Tensor of shape [channels, shape].
      epoch (int): current training epoch.
      index (int): helpful to differentiate samples from the same batch.
      conditions (dict[str, str], optional): conditioning used during generation.
      prompt_wav (torch.Tensor, optional): prompt used during generation. Tensor of shape [channels, shape].
      ground_truth_wav (torch.Tensor, optional): reference audio where prompt was extracted from.
          Tensor of shape [channels, shape].
      generation_args (dict[str, any], optional): dictionary of other arguments used during generation.
  Returns:
      Sample: The saved sample.
docstr_tok: ["Adds","a","single","sample",".","The","sample","is","stored","in","the","XP","'s","sample","output","directory",",","under","a","corresponding","epoch","folder",".","Each","sample","is","assigned","an","id","which","is","computed","using","the","input","data",".","In","addition","to","the","sample","itself",",","a","json","file","containing","associated","metadata","is","stored","next","to","it",".","Args",":","sample_wav","(","torch.Tensor",")",":","sample","audio","to","store",".","Tensor","of","shape","[","channels",",","shape","]",".","epoch","(","int",")",":","current","training","epoch",".","index","(","int",")",":","helpful","to","differentiate","samples","from","the","same","batch",".","conditions","(","dict","[","str",",","str","]",",","optional",")",":","conditioning","used","during","generation",".","prompt_wav","(","torch.Tensor",",","optional",")",":","prompt","used","during","generation",".","Tensor","of","shape","[","channels",",","shape","]",".","ground_truth_wav","(","torch.Tensor",",","optional",")",":","reference","audio","where","prompt","was","extracted","from",".","Tensor","of","shape","[","channels",",","shape","]",".","generation_args","(","dict","[","str",",","any","]",",","optional",")",":","dictionary","of","other","arguments","used","during","generation",".","Returns",":","Sample",":","The","saved","sample","."]
code: def add_sample(
      self,
      sample_wav: torch.Tensor,
      epoch: int,
      index: int = 0,
      conditions: tp.Optional[tp.Dict[str, str]] = None,
      prompt_wav: tp.Optional[torch.Tensor] = None,
      ground_truth_wav: tp.Optional[torch.Tensor] = None,
      generation_args: tp.Optional[tp.Dict[str, tp.Any]] = None,
  ) -> Sample:
      """Adds a single sample.
      The sample is stored in the XP's sample output directory, under a corresponding epoch folder.
      Each sample is assigned an id which is computed using the input data. In addition to the
      sample itself, a json file containing associated metadata is stored next to it.
  
      Args:
          sample_wav (torch.Tensor): sample audio to store. Tensor of shape [channels, shape].
          epoch (int): current training epoch.
          index (int): helpful to differentiate samples from the same batch.
          conditions (dict[str, str], optional): conditioning used during generation.
          prompt_wav (torch.Tensor, optional): prompt used during generation. Tensor of shape [channels, shape].
          ground_truth_wav (torch.Tensor, optional): reference audio where prompt was extracted from.
              Tensor of shape [channels, shape].
          generation_args (dict[str, any], optional): dictionary of other arguments used during generation.
      Returns:
          Sample: The saved sample.
      """
      sample_id = self._get_sample_id(index, prompt_wav, conditions)
      reuse_id = self.map_reference_to_sample_id
      prompt, ground_truth = None, None
      if prompt_wav is not None:
          prompt_id = (
              sample_id
              if reuse_id
              else self._get_tensor_id(prompt_wav.sum(0, keepdim=True))
          )
          prompt_duration = (
              prompt_wav.shape[-1] / self.xp.cfg.sample_rate
          )
          prompt_path = self._store_audio(
              prompt_wav,
              self.base_folder / str(epoch) / "prompt" / prompt_id,
          )
          prompt = ReferenceSample(
              prompt_id, str(prompt_path), prompt_duration
          )
      if ground_truth_wav is not None:
          ground_truth_id = (
              sample_id
              if reuse_id
              else self._get_tensor_id(
                  ground_truth_wav.sum(0, keepdim=True)
              )
          )
          ground_truth_duration = (
              ground_truth_wav.shape[-1] / self.xp.cfg.sample_rate
          )
          ground_truth_path = self._store_audio(
              ground_truth_wav,
              self.base_folder / "reference" / ground_truth_id,
          )
          ground_truth = ReferenceSample(
              ground_truth_id,
              str(ground_truth_path),
              ground_truth_duration,
          )
      sample_path = self._store_audio(
          sample_wav,
          self.base_folder / str(epoch) / sample_id,
          overwrite=True,
      )
      duration = sample_wav.shape[-1] / self.xp.cfg.sample_rate
      sample = Sample(
          sample_id,
          str(sample_path),
          epoch,
          duration,
          conditions,
          prompt,
          ground_truth,
          generation_args,
      )
      self.samples.append(sample)
      with open(sample_path.with_suffix(".json"), "w") as f:
          json.dump(asdict(sample), f, indent=2)
      return sample
  
imports: from concurrent.futures import ThreadPoolExecutor
  from dataclasses import asdict, dataclass
  from functools import lru_cache
  import hashlib
  import json
  import logging
  from pathlib import Path
  import re
  import typing
  import unicodedata
  import uuid
  import dora
  import torch
  from ...data.audio import audio_read, audio_write
label_desc: Use image node_id 9 for calling the SampleManager obj's underlying member method code with example usage: obj.add_sample(sample_wav, epoch, index, conditions, prompt_wav, ground_truth_wav, generation_args) and returns: sample