import math
import numpy as np
import librosa
from utils import hparams as hp
from scipy.signal import lfilter
import soundfile as sf


def label_2_float(x, bits):
    return 2 * x / (2**bits - 1.) - 1.


def float_2_label(x, bits):
    assert abs(x).max() <= 1.0
    x = (x + 1.) * (2**bits - 1) / 2
    return x.clip(0, 2**bits - 1)


def load_wav(path):
    return librosa.load(path, sr=hp.sample_rate)[0]


def save_wav(x, path):
    # librosa.output.write_wav(path, x.astype(np.float32), sr=hp.sample_rate)
    sf.write(path, x.astype(np.float32), samplerate=hp.sample_rate)

def split_signal(x):
    unsigned = x + 2**15
    coarse = unsigned // 256
    fine = unsigned % 256
    return coarse, fine


def combine_signal(coarse, fine):
    return coarse * 256 + fine - 2**15


def encode_16bits(x):
    return np.clip(x * 2**15, -2**15, 2**15 - 1).astype(np.int16)


def linear_to_mel(spectrogram):
    return librosa.feature.melspectrogram(
        S=spectrogram, sr=hp.sample_rate, n_fft=hp.n_fft, n_mels=hp.num_mels, fmin=hp.fmin)

'''
def build_mel_basis():
    return librosa.filters.mel(hp.sample_rate, hp.n_fft, n_mels=hp.num_mels, fmin=hp.fmin)
'''

def normalize(S):
    return np.clip((S - hp.min_level_db) / -hp.min_level_db, 0, 1)


def denormalize(S):
    return (np.clip(S, 0, 1) * -hp.min_level_db) + hp.min_level_db


def amp_to_db(x):
    return 20 * np.log10(np.maximum(1e-5, x))


def db_to_amp(x):
    return np.power(10.0, x * 0.05)


def spectrogram(y):
    D = stft(y)
    S = amp_to_db(np.abs(D)) - hp.ref_level_db
    return normalize(S)


def melspectrogram(y):
    D = stft(y)
    S = amp_to_db(linear_to_mel(np.abs(D)))
    return normalize(S)


def stft(y):
    return librosa.stft(
        y=y,
        n_fft=hp.n_fft, hop_length=hp.hop_length, win_length=hp.win_length)


def pre_emphasis(x):
    return lfilter([1, -hp.preemphasis], [1], x)


def de_emphasis(x):
    return lfilter([1], [1, -hp.preemphasis], x)


def encode_mu_law(x, mu):
    mu = mu - 1
    fx = np.sign(x) * np.log(1 + mu * np.abs(x)) / np.log(1 + mu)
    return np.floor((fx + 1) / 2 * mu + 0.5)


def decode_mu_law(y, mu, from_labels=True):
    # TODO: get rid of log2 - makes no sense
    if from_labels: y = label_2_float(y, math.log2(mu))
    mu = mu - 1
    x = np.sign(y) / mu * ((1 + mu) ** np.abs(y) - 1)
    return x

def reconstruct_waveform(mel, n_iter=32):
    """Uses Griffin-Lim phase reconstruction to convert from a normalized
    mel spectrogram back into a waveform."""
    denormalized = denormalize(mel)
    amp_mel = db_to_amp(denormalized)
    S = librosa.feature.inverse.mel_to_stft(
        amp_mel, power=1, sr=hp.sample_rate,
        n_fft=hp.n_fft, fmin=hp.fmin)
    wav = librosa.core.griffinlim(
        S, n_iter=n_iter,
        hop_length=hp.hop_length, win_length=hp.win_length)
    return wav


[((43, 11, 44, 91), 'librosa.feature.melspectrogram', 'librosa.feature.melspectrogram', (), '', False, 'import librosa\n'), ((52, 11, 52, 66), 'numpy.clip', 'np.clip', ({(52, 19, 52, 59): '((S - hp.min_level_db) / -hp.min_level_db)', (52, 61, 52, 62): '(0)', (52, 64, 52, 65): '(1)'}, {}), '((S - hp.min_level_db) / -hp.min_level_db, 0, 1)', True, 'import numpy as np\n'), ((64, 11, 64, 35), 'numpy.power', 'np.power', ({(64, 20, 64, 24): '(10.0)', (64, 26, 64, 34): '(x * 0.05)'}, {}), '(10.0, x * 0.05)', True, 'import numpy as np\n'), ((80, 11, 82, 75), 'librosa.stft', 'librosa.stft', (), '', False, 'import librosa\n'), ((86, 11, 86, 48), 'scipy.signal.lfilter', 'lfilter', ({(86, 19, 86, 39): '[1, -hp.preemphasis]', (86, 41, 86, 44): '[1]', (86, 46, 86, 47): 'x'}, {}), '([1, -hp.preemphasis], [1], x)', False, 'from scipy.signal import lfilter\n'), ((90, 11, 90, 48), 'scipy.signal.lfilter', 'lfilter', ({(90, 19, 90, 22): '[1]', (90, 24, 90, 44): '[1, -hp.preemphasis]', (90, 46, 90, 47): 'x'}, {}), '([1], [1, -hp.preemphasis], x)', False, 'from scipy.signal import lfilter\n'), ((96, 11, 96, 44), 'numpy.floor', 'np.floor', ({(96, 20, 96, 43): '((fx + 1) / 2 * mu + 0.5)'}, {}), '((fx + 1) / 2 * mu + 0.5)', True, 'import numpy as np\n'), ((111, 8, 113, 37), 'librosa.feature.inverse.mel_to_stft', 'librosa.feature.inverse.mel_to_stft', (), '', False, 'import librosa\n'), ((114, 10, 116, 59), 'librosa.core.griffinlim', 'librosa.core.griffinlim', (), '', False, 'import librosa\n'), ((20, 11, 20, 48), 'librosa.load', 'librosa.load', (), '', False, 'import librosa\n'), ((95, 51, 95, 65), 'numpy.log', 'np.log', ({(95, 58, 95, 64): '(1 + mu)'}, {}), '(1 + mu)', True, 'import numpy as np\n'), ((39, 11, 39, 48), 'numpy.clip', 'np.clip', ({(39, 19, 39, 28): '(x * 2 ** 15)', (39, 30, 39, 36): '(-2 ** 15)', (39, 38, 39, 47): '(2 ** 15 - 1)'}, {}), '(x * 2 ** 15, -2 ** 15, 2 ** 15 - 1)', True, 'import numpy as np\n'), ((56, 12, 56, 28), 'numpy.clip', 'np.clip', ({(56, 20, 56, 21): 'S', (56, 23, 56, 24): '(0)', (56, 26, 56, 27): '(1)'}, {}), '(S, 0, 1)', True, 'import numpy as np\n'), ((60, 25, 60, 44), 'numpy.maximum', 'np.maximum', ({(60, 36, 60, 40): '(1e-05)', (60, 42, 60, 43): 'x'}, {}), '(1e-05, x)', True, 'import numpy as np\n'), ((69, 18, 69, 27), 'numpy.abs', 'np.abs', ({(69, 25, 69, 26): 'D'}, {}), '(D)', True, 'import numpy as np\n'), ((75, 32, 75, 41), 'numpy.abs', 'np.abs', ({(75, 39, 75, 40): 'D'}, {}), '(D)', True, 'import numpy as np\n'), ((95, 9, 95, 19), 'numpy.sign', 'np.sign', ({(95, 17, 95, 18): 'x'}, {}), '(x)', True, 'import numpy as np\n'), ((101, 41, 101, 54), 'math.log2', 'math.log2', ({(101, 51, 101, 53): 'mu'}, {}), '(mu)', False, 'import math\n'), ((103, 8, 103, 18), 'numpy.sign', 'np.sign', ({(103, 16, 103, 17): 'y'}, {}), '(y)', True, 'import numpy as np\n'), ((103, 39, 103, 48), 'numpy.abs', 'np.abs', ({(103, 46, 103, 47): 'y'}, {}), '(y)', True, 'import numpy as np\n'), ((95, 38, 95, 47), 'numpy.abs', 'np.abs', ({(95, 45, 95, 46): 'x'}, {}), '(x)', True, 'import numpy as np\n')]