def test_reader_pitch_metadata(): """ Ensure WaveReader obtains root pitch from wav filename. """ cfg_dict = yaml.load(SINE) # root_pitch should be obtained from filename read = WaveReader(WAVE_DIR, n163_cfg(**cfg_dict)) read.read() cfg_dict['wav_path'] = 'Sample 19.wav' with pytest.raises(TypeError): read = WaveReader(WAVE_DIR, n163_cfg(**cfg_dict)) read.read()
def test_reader_sweep(cfg): cfg = dataclasses.replace(cfg, sweep='0 0 1 1') read = WaveReader(CFG_DIR, cfg) instr = read.read() assert len(instr.waves) == 2 assert (instr.sweep == [0, 0, 1, 1]).all()
def test_reader_multi_waves(): cfg_str = '''\ files: - path: sine440.69.wav - path: sine440.69.wav speed: 2 volume: 1.1 - path: sine256.59.624.wav speed: 3 volume: 0 pitch_estimate: 69 nwave: 1 nsamp: 16 ''' harmonics = [1, 2] # plus 3 at volume=0 cfg = n163_cfg(**yaml.load(cfg_str)) read = WaveReader(WAVE_DIR, cfg) instr = read.read() # Calculate spectrum of resulting signal spectrum: np.ndarray = np.abs(rfft_norm(instr.waves[0])) spectrum[0] = 0 threshold = np.amax(spectrum) / 2 assert threshold > 0.1 # Ensure pitches present assert (spectrum[harmonics] > threshold).all() # Ensure no other pitches present spectrum[harmonics] = 0 spectrum[0] = 0 assert (spectrum < threshold).all()
def get_phase(cfg): read = WaveReader(WAVE_DIR, cfg) instr = read.read() spectrum = rfft_norm(instr.waves[0]) phase = np.angle(spectrum[1]) return phase
def test_reader_stft_merge(cfg, stft_merge): cfg.stft_merge = stft_merge read = WaveReader(CFG_DIR, cfg) instr = read.read() # TODO check instr is legit assert instr
def test_reader_subsample(): wave_sub = 2 env_sub = 3 # should wave mandatory =N*env? for ntick in [3, 4]: cfg = n163_cfg( wav_path=WAV_PATH, nsamp=NSAMP, fps=60, pitch_estimate=74, nwave=ntick, wave_sub=wave_sub, env_sub=env_sub, ) read = WaveReader(CFG_DIR, cfg) instr = read.read() # Subsampled waves nwave_sub = ceildiv(ntick, wave_sub) assert len(instr.waves) == nwave_sub # Subsampled sweep assert (instr.sweep == np.arange(nwave_sub)).all() # Subsampled volume/pitch nenv_sub = ceildiv(ntick, env_sub) def check(arr: np.ndarray): assert len(arr) == nenv_sub for i in range(ceildiv(ntick, env_sub)): check(instr.vols) check(instr.freqs)
def test_reader_wave_locs(cfg): cfg = dataclasses.replace(cfg, wave_locs='0 1 3 6') read = WaveReader(CFG_DIR, cfg) instr = read.read() assert len(instr.waves) == 4 assert (instr.sweep == map(int, '0 1 1 2 2 2 3'.split())).all()
def main(): # Disable volume ratio compensation. File.VOLUME_RATIO = 1 # ratios = [] cfg_ratio = {} for width_ms in list(range(15, 40 + 1, 5)) + [None]: print('width_ms =', width_ms) for cfg_path in sorted(Path().glob('*.wtcfg')): # print(cfg_path) file_cfg: dict = yaml.load(cfg_path) cfg = to_brr.WavetableConfig(**file_cfg) if width_ms: cfg.width_ms = width_ms wr = WaveReader(cfg_path.parent, cfg) cfg_ratio[cfg_path] = calc_ratio(wr) # ratios.append(calc_ratio(wr)) ratios = list(cfg_ratio.values()) if width_ms is None: for k, v in cfg_ratio.items(): print(k.stem, '', v) print() # print('Mean: ', np.mean(ratios)) # print('Median: ', np.median(ratios)) print('GMean: ', gmean(ratios)) print()
def test_reader_sweep_remove_unused(cfg): """ Ensure that waves not present in `sweep` are removed. """ cfg = dataclasses.replace(cfg, sweep='0 1 3 6 3 1') read = WaveReader(CFG_DIR, cfg) instr = read.read() assert len(instr.waves) == 4 assert (instr.sweep == [0, 1, 2, 3, 2, 1]).all()
def test_reader_sweep_loop_release(cfg): cfg = dataclasses.replace(cfg, sweep='0 / 0 | 1 1') read = WaveReader(CFG_DIR, cfg) instr = read.read() assert len(instr.waves) == 2 assert (instr.sweep == [0, 0, 1, 1]).all() assert instr.sweep.loop == 2 assert instr.sweep.release == 1
def stereo_read(stereo_cfg) -> WaveReader: return WaveReader(CFG_DIR, stereo_cfg)
def read(cfg) -> WaveReader: return WaveReader(CFG_DIR, cfg)
def process_cfg(global_cli: ExtractorCLI, cfg_path: Path) -> WavetableMetadata: """ Reads cfg_path. Writes intermediate wav files to `cfg_dir/WAV_SUBDIR/cfg-012.wav`. Writes brr files to `global_cfg.dest_dir/cfg-012.brr`. The WAV file contains data + data[:unlooped_prefix]. The BRR file loops WAV[unlooped_prefix:]. """ if not cfg_path.is_file(): raise ValueError(f'invalid cfg_path {cfg_path}, is not file') # Initialize directories cfg_name = cfg_path.stem cfg_dir = cfg_path.parent # type: Path wav_dir = cfg_dir / WAV_SUBDIR brr_dir = global_cli.dest_dir wav_dir.mkdir(exist_ok=True) for file in wav_dir.glob(f'{cfg_name}-*'): file.unlink() for file in brr_dir.glob(f'{cfg_name}-*'): file.unlink() # Load config file file_cfg: dict = yaml.load(cfg_path) cfg = WavetableConfig(**file_cfg) no_brr = cfg.no_brr unlooped_prefix = cfg.unlooped_prefix truncate_prefix = cfg.truncate_prefix gaussian = cfg.gaussian brr_arg = BrrEncoderArgs( gaussian=gaussian, loop=unlooped_prefix, ) # Generate wavetables wr = WaveReader(cfg_path.parent, cfg) instr = wr.read() ntick = wr.ntick print(f' {cfg_name}: ntick={ntick}') for i, wave in enumerate(instr.waves): wave_name = f'{cfg_name}-{i:03}' wav_path = (wav_dir / wave_name).with_suffix('.wav') wav_path.parent.mkdir(exist_ok=True) # Write WAV file wave = np.around(wave) wave_int = wave.astype(DTYPE) if any(wave_int != wave): # Overflows are not automatically caught. # https://github.com/numpy/numpy/issues/8987 raise ValueError('Integer overflow when writing .wav!') wave = wave_int # Duplicate prefix of wave data wave = np.concatenate((wave, wave[:unlooped_prefix])) wavfile.write(str(wav_path), SAMPLE_RATE, wave) if no_brr: continue # Encode BRR file brr_path = (brr_dir / wave_name).with_suffix('.brr') brr = BrrEncoder(brr_arg, wav_path, brr_path) # The first sample's "prefix" is used. When we switch loop points to subsequent # samples, only their looped portions are used. if truncate_prefix and i != 0: brr.write(behead=True) else: brr.write() # Generate metadata pitches: List[float] = freq2midi(instr.freqs).tolist() return WavetableMetadata( nsamp=cfg.nsamp, ntick=ntick, fps=cfg.fps, wave_sub=cfg.wave_sub, env_sub=cfg.env_sub, root_pitch=cfg.root_pitch, pitches=pitches, )