"""Prepare acoustic features for one-to-one voice conversion.

usage:
    prepare_features_vc.py [options] <DATA_ROOT> <source_speaker> <target_speaker>

options:
    --max_files=<N>      Max num files to be collected. [default: 100]
    --dst_dir=<d>        Destination directory [default: data/cmu_arctic_vc].
    --overwrite          Overwrite files.
    -h, --help           show this help message and exit
"""
from __future__ import division, print_function, absolute_import

from docopt import docopt
import numpy as np

from nnmnkwii.datasets import FileSourceDataset
from nnmnkwii import preprocessing as P
from nnmnkwii.preprocessing.alignment import DTWAligner
from nnmnkwii.datasets import cmu_arctic, voice_statistics, vcc2016

import pysptk
import pyworld
from scipy.io import wavfile
from tqdm import tqdm
from os.path import basename, splitext, exists, expanduser, join, dirname
import os
import sys

from hparams import vc as hp
from hparams import hparams_debug_string


# vcc2016.WavFileDataSource and voice_statistics.WavFileDataSource can be
# drop-in replacement. See below for details:
# https://r9y9.github.io/nnmnkwii/latest/references/datasets.html#builtin-data-sources
class MGCSource(cmu_arctic.WavFileDataSource):
    def __init__(self, data_root, speakers, max_files=None):
        super(MGCSource, self).__init__(data_root, speakers,
                                        max_files=max_files)
        self.alpha = None

    def collect_features(self, wav_path):
        fs, x = wavfile.read(wav_path)
        x = x.astype(np.float64)
        f0, timeaxis = pyworld.dio(x, fs, frame_period=hp.frame_period)
        f0 = pyworld.stonemask(x, f0, timeaxis, fs)
        spectrogram = pyworld.cheaptrick(x, f0, timeaxis, fs)
        spectrogram = P.trim_zeros_frames(spectrogram)
        if self.alpha is None:
            self.alpha = pysptk.util.mcepalpha(fs)
        mgc = pysptk.sp2mc(spectrogram, order=hp.order, alpha=self.alpha)
        # Drop 0-th coefficient
        mgc = mgc[:, 1:]
        # 50Hz cut-off MS smoothing
        hop_length = int(fs * (hp.frame_period * 0.001))
        modfs = fs / hop_length
        mgc = P.modspec_smoothing(mgc, modfs, cutoff=50)
        # Add delta
        mgc = P.delta_features(mgc, hp.windows)
        return mgc.astype(np.float32)


if __name__ == "__main__":
    args = docopt(__doc__)
    print("Command line args:\n", args)
    DATA_ROOT = args["<DATA_ROOT>"]
    source_speaker = args["<source_speaker>"]
    target_speaker = args["<target_speaker>"]
    max_files = int(args["--max_files"])
    dst_dir = args["--dst_dir"]
    overwrite = args["--overwrite"]

    print(hparams_debug_string(hp))

    X_dataset = FileSourceDataset(MGCSource(DATA_ROOT, [source_speaker],
                                            max_files=max_files))
    Y_dataset = FileSourceDataset(MGCSource(DATA_ROOT, [target_speaker],
                                            max_files=max_files))

    skip_feature_extraction = exists(join(dst_dir, "X")) \
        and exists(join(dst_dir, "Y"))
    if overwrite:
        skip_feature_extraction = False
    if skip_feature_extraction:
        print("Features seems to be prepared, skipping feature extraction.")
        sys.exit(0)

    # Create dirs
    for speaker, name in [(source_speaker, "X"), (target_speaker, "Y")]:
        d = join(dst_dir, name)
        print("Destination dir for {}: {}".format(speaker, d))
        if not exists(d):
            os.makedirs(d)

    # Convert to arrays
    print("Convert datasets to arrays")
    X, Y = X_dataset.asarray(verbose=1), Y_dataset.asarray(verbose=1)

    # Alignment
    print("Perform alignment")
    X, Y = DTWAligner().transform((X, Y))

    print("Save features to disk")
    for idx, (x, y) in tqdm(enumerate(zip(X, Y))):
        # paths
        src_name = splitext(basename(X_dataset.collected_files[idx][0]))[0]
        tgt_name = splitext(basename(Y_dataset.collected_files[idx][0]))[0]
        src_path = join(dst_dir, "X", src_name)
        tgt_path = join(dst_dir, "Y", tgt_name)

        # Trim and ajast frames
        x = P.trim_zeros_frames(x)
        y = P.trim_zeros_frames(y)
        x, y = P.adjust_frame_lengths(x, y, pad=True, divisible_by=2)

        # Save
        np.save(src_path, x)
        np.save(tgt_path, y)

[(65, 'docopt.docopt', 'docopt', False, 'from docopt import docopt\n'), (44, 'scipy.io.wavfile.read', 'wavfile.read', False, 'from scipy.io import wavfile\n'), (46, 'pyworld.dio', 'pyworld.dio', False, 'import pyworld\n'), (47, 'pyworld.stonemask', 'pyworld.stonemask', False, 'import pyworld\n'), (48, 'pyworld.cheaptrick', 'pyworld.cheaptrick', False, 'import pyworld\n'), (49, 'nnmnkwii.preprocessing.trim_zeros_frames', 'P.trim_zeros_frames', True, 'from nnmnkwii import preprocessing as P\n'), (52, 'pysptk.sp2mc', 'pysptk.sp2mc', False, 'import pysptk\n'), (58, 'nnmnkwii.preprocessing.modspec_smoothing', 'P.modspec_smoothing', True, 'from nnmnkwii import preprocessing as P\n'), (60, 'nnmnkwii.preprocessing.delta_features', 'P.delta_features', True, 'from nnmnkwii import preprocessing as P\n'), (74, 'hparams.hparams_debug_string', 'hparams_debug_string', False, 'from hparams import hparams_debug_string\n'), (87, 'sys.exit', 'sys.exit', False, 'import sys\n'), (91, 'os.path.join', 'join', False, 'from os.path import basename, splitext, exists, expanduser, join, dirname\n'), (109, 'os.path.join', 'join', False, 'from os.path import basename, splitext, exists, expanduser, join, dirname\n'), (110, 'os.path.join', 'join', False, 'from os.path import basename, splitext, exists, expanduser, join, dirname\n'), (113, 'nnmnkwii.preprocessing.trim_zeros_frames', 'P.trim_zeros_frames', True, 'from nnmnkwii import preprocessing as P\n'), (114, 'nnmnkwii.preprocessing.trim_zeros_frames', 'P.trim_zeros_frames', True, 'from nnmnkwii import preprocessing as P\n'), (115, 'nnmnkwii.preprocessing.adjust_frame_lengths', 'P.adjust_frame_lengths', True, 'from nnmnkwii import preprocessing as P\n'), (118, 'numpy.save', 'np.save', True, 'import numpy as np\n'), (119, 'numpy.save', 'np.save', True, 'import numpy as np\n'), (51, 'pysptk.util.mcepalpha', 'pysptk.util.mcepalpha', False, 'import pysptk\n'), (81, 'os.path.join', 'join', False, 'from os.path import basename, splitext, exists, expanduser, join, dirname\n'), (82, 'os.path.join', 'join', False, 'from os.path import basename, splitext, exists, expanduser, join, dirname\n'), (93, 'os.path.exists', 'exists', False, 'from os.path import basename, splitext, exists, expanduser, join, dirname\n'), (94, 'os.makedirs', 'os.makedirs', False, 'import os\n'), (102, 'nnmnkwii.preprocessing.alignment.DTWAligner', 'DTWAligner', False, 'from nnmnkwii.preprocessing.alignment import DTWAligner\n'), (107, 'os.path.basename', 'basename', False, 'from os.path import basename, splitext, exists, expanduser, join, dirname\n'), (108, 'os.path.basename', 'basename', False, 'from os.path import basename, splitext, exists, expanduser, join, dirname\n')]