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 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 calc_ratio(wr: WaveReader) -> float: assert len(wr.files) == 1 file = wr.files[0] # Hook the file to save input data. # [idx][time] = value list_data: List[np.ndarray] = [] def save_output(_channel_data_at): @wraps(_channel_data_at) def wrapper(*args, **kwargs): channel_data: np.ndarray = _channel_data_at(*args, **kwargs) assert len(channel_data) == 1 data = channel_data[0].copy() list_data.append(data) return channel_data return wrapper file._channel_data_at = save_output(file._channel_data_at) # Read output data. instr = wr.read() waves = instr.waves assert len(waves) == len(list_data) # Compare magnitude of input and output data. wave_ratios = [] for input, output in zip(list_data, waves): wave_ratios.append(magnitude(output) / magnitude(input)) # Using return float(np.mean(wave_ratios))
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, )