view utils.py @ 8:d5693c7aa0ae

* Excitation pattern extraction is much faster. Calculating partial loudness for three 2 second components is reduced from ~180s to ~25s. * runScript1.py is a script to get loudness data of all Stimuli within a set data tree. Not useful for anyone else. * testELC.py calculates the equal loudness contours. Can also be used to get the equal loudness of two sounds. When I get the time, this will be added to a loudness package. * loudnessRatios performs some descriptive statistics on loudness data obtained from the data within the set file tree. Probably not useful for anybody else but the author.
author Carl Bussey <c.bussey@se10.qmul.ac.uk>
date Mon, 31 Mar 2014 19:22:23 +0100
parents d7b2784ff5a3
children ef62cca13f36
line wrap: on
line source
"""
A utility module to assist cortical_model.py with general procedures, e.g., file reading and writing.

Packaged dependencies:
    * erb.dat
    * outMidFir.dat
    * tq.dat
    
External dependencies:
    * scipy
    * numpy
    * matplotlib
"""

import numpy as np
import scipy.io.wavfile as wav
import matplotlib.pyplot as plt
import scipy.fftpack as fft
import wave
import struct
from os import getcwd

gPath = getcwd()

def set_path_to_data(path):

    global gPath
    gPath = path

    return

def load_erb_data():
	"""
	Loads and returns 39 ERB frequencies and bandwidths
    
    Parameters:
        NONE
        
    Returns:
        * fc (type: numpy array of floats) - a vector of length 39 with elements containing
            the centre frequencies of each ERB.
        * bw (type: numpy array of floats) - a vector of length 39 with elements containing
            the bandwidths of each ERB.
    
    Dependencies:
        * erb.dat (place in the same folder as utils.pyc)
	"""
	# read data from a text file
	data=np.loadtxt(gPath + "/erb.dat",delimiter=",")

	# load centre frequencies from the first column into fc
	fc=np.array(data[:,0])
	# load bandwidths from the second column into bw
	bw=np.array(data[:,1])

	return fc,bw

def load_outMidFir_coeff():
    """
    Loads and returns 39 ERB frequencies and bandwidths
        
    Parameters:
        NONE
        
    Returns:
        * b (type: numpy array of floats) - a vector of length 4097 containing the impulse
            response of the outer middle ear.
        
    Dependencies:
        * outMidFir.dat (place in the same folder as utils.pyc)
    """

    b=np.array(np.loadtxt(gPath + "/outMidFir.dat", delimiter=","))

    return b

def load_sl_parameters():
    """
    Loads the loudness parameters for each ERB fc into a tuple. A shortcut for load_tq_data(), load_A_data() and load_alpha_data().
        
    Parameters:
        NONE
        
    Returns:
        * tq (type: numpy array of floats) - a vector of length 39 containing the threshold excitation
        intensity in quietat each ERB centre frequency
        * A (type: numpy array of floats) - a vector of length 39 containing the parameter A for each ERB fc
        * alpha (type: numpy array of floats) - a vector of length 39 containing the parameter alpha for each ERB fc
        
    Dependencies:
        * tq.dat (place in the same folder as utils.pyc)
        * A.dat (place in the same folder as utils.pyc)
        * alpha.dat (place in the same folder as utils.pyc)
    """
    tq_dB = load_tqdB_data()
    A = load_A_data()
    alpha = load_alpha_data()

    return tq_dB, A, alpha

def load_tqdB_data():
    """
    Loads and returns the excitation threshold of quiet for each ERB fc.
    
    Parameters:
        NONE
    
    Returns:
        * tq (type: numpy array of floats) - a vector of length 39 containing the threshold excitation
        intensity in quietat each ERB centre frequency
    
    Dependencies:
        * tq.dat (place in the same folder as utils.pyc)
    """

    tq = np.array(np.loadtxt(gPath + "/tq_dB.dat",delimiter=","))

    return tq

def load_A_data():
    """
    Loads and returns the excitation A parameters for each ERB fc.
        
    Parameters:
        NONE
        
    Returns:
        * A (type: numpy array of floats) - a vector of length 39 containing the parameter A for each ERB fc
        
    Dependencies:
        * A.dat (place in the same folder as utils.pyc)
    """
    
    A = np.array(np.loadtxt(gPath + "/A.dat",delimiter=","))
    
    return A

def load_alpha_data():
    """
    Loads and returns the excitation alpha parameters for each ERB fc.
        
    Parameters:
        NONE
        
    Returns:
        * alpha (type: numpy array of floats) - a vector of length 39 containing the parameter alpha for each ERB fc
        
    Dependencies:
        * alpha.dat (place in the same folder as utils.pyc)
    """
    
    alpha = np.array(np.loadtxt(gPath + "/alpha.dat",delimiter=","))
    
    return alpha

def exp_sequence(start, stop, n, base=2):
    """
    Creates a linear sequence with n points starting from start and ending at stop. For each
    element in the sequence, i, the output sequence is 2**i, generating an exponential sequence
    with base 2.
    
    Parameters:
        * start (type: numerical int) - determines the first element of the sequence, i.e.,
            base**start. (Required)
        * stop (type: numerical int) - determines the last element of the sequence, i.e.,
            base**stop. (Required)
        * n (type = numerical int) - determines the number of elements in the sequence. (Required)
        * base (type: numerical) - determines the exponential base. (Optional; Default = 2)
    
    Returns:
        * seq - the exponential sequence
    """
    
    seq = [base**x for x in np.linspace(start,stop,n)]
    
    return seq

def wavread(file):
    """
    Reads the audio data from a wav file and converts it to floating point ranging from -1 to 1.
    
    Parameters:
        * file (type: string) - the name of the wav file to read from. (Required)
        
    Returns:
        * fs (type: numerical) - the sampling frequency of the signal storied in file
        * data (type: numpy array of floats) - the data of the signal stored in file normalised
            to an amplitude range of -1 to 1.
    """

    fs,data = wav.read(file)
    data = np.array(int_to_nfloat(data))

    return fs,data

def wavread2(filename):

    file = wave.open(filename)
    nChans, sampwidth, fs, nFrames, comptype, compname = file.getparams()
    
    N = 8*sampwidth
    
    data = np.zeros((nChans, nFrames))
    
    if sampwidth == 2:
        unpackChar = 'h'
    elif sampwidth == 3:
        unpackChar = 'hc'
    elif sampwidth == 4:
        unpackChar = 'i'

    frame = [[] for i in range(nChans)]

    
    for i in range(nFrames):
        bytes = file.readframes(1)
        
        if nChans > 1:
            for j in range(nChans):
                index = j*sampwidth
                frame[j] = bytes[index:index+sampwidth]
        else:
            frame[0] = bytes

        for j in range(nChans):
            if sampwidth == 2:
                data[j][i] = struct.unpack('<h', frame[j])[0]
            elif sampwidth == 3:
                data[j][i] = struct.unpack('<i', frame[j] + ('\0' if frame[j][2] < '\x80' else '\xff'))[0]
            elif sampwidth == 4:
                data[j][i] = struct.unpack('<i', frame[j])[0]

    data[data<0] = data[data<0]/(2**N)
    data[data>0] = data[data>0]/((2**N)-1)

    if nChans == 1:
        data = data.reshape((nFrames))

    file.close()

    return fs,data

def wavwrite(file, fs, data, precision = 16):
    """
    Unnormalises the audio data to a specified integer precision and converts to an int, then 
    writes the audio data to a wav file. (E.g., if 16-bit precision, then highest amplitude
    is equal to 2**16).
        
    Parameters:
        * file (type: string) - the name of the wav file to write to. (Required)
        * fs (type: numerical) - the sampling frequency of the signal. (Required)
        * data (type: array-like matrix of floats) - the signal data normalised from -1 to 1. The signal will be clipped if not
        in this range. (Required)
        * precision (type: numerical int)- the bit precision to store at. Can only be 16 or 32 bit, because that is
        all scipy allows.
        
    Returns:
        NONE
        
    TODO explore WAVE package to be allow for 24bit precision.
    """
    
    data[data>1] = 1
    data[data<-1] = -1
    
    if(precision == 16):
        dtype = np.int16
    elif(precision == 32):
        dtype = np.int32
    else:
        print "Error: precision can only be 16 or 32 bit due to scipy package."
        return

    data = nfloat_to_int(data)
    wav.write(file, fs, data)
    
    return

def plot_fft(x, xscale = 'log', yscale = 'log', show = True):
    """
    Plots the fft of signal x. If the figure is not shown, the current plot is held to allow other plots to
    be added to the same figure.
    
    Parameters:
        * x (type: array-like matrix of floats) - the signal to be analysed. (Required)
        * xscale (type: string) - the scale of the frequency axis. Values are 'log' or 'linear'. (Optional; Default = 'log')
        * yscale (type: string) - the scale of the amplitude axis. Values are 'log' or 'linear'. (Optional; Default = 'log')
        * show (type: boolean) - specifies whether the figure should be shown. If False, the current plot will be held so
        other plots can be added to the figure. (Optional; Default = True)
        
    Returns:
        NONE
    """

    plt.gca().set_xscale(xscale)
    plt.gca().set_yscale(yscale)
    
    x = np.array(x)
    fftx = np.absolute(fft.fft(x))
    plt.plot(range(np.shape(x)[0]),fftx)
    if(show):
        plt.show()
    else:
        plt.hold(True)

    return

def plot_waveform(x, show = True):
    """
    Plots the waveform of signal x. If the figure is not shown, the current plot is held to allow other plots to
    be added to the same figure.
        
    Parameters:
        * x (type: array-like matrix of floats) - the signal to be plotted. (Required)
        * show (type: boolean) - specifies whether the figure should be shown. If False, the current plot will be held so
        other plots can be added to the figure. (Optional; Default = True)
        
    Returns:
        NONE
    """
    
    x = np.array(x)
    plt.plot(x)
    
    if(show): plt.show()
    else: plt.hold(True)

    return

def int_to_nfloat(input, outputtype=np.float32):
    """
    Convert integer with to floating point with a range from -1 to 1.
        
    Parameters:
        * input (type: array-like matrix of ints) - a signed integer array. (Required)
        dtype : the output datatype. (Optional; Default = np.float32)
        
    Returns:
        * y (type: array-like matrix of floats) - a float array normalised to a
            range of -1 to 1.
    """
    
    input = np.array(input)
    assert input.dtype.kind == 'i', "'input' must be an array-like matrix of integers."
    outputtype = np.dtype(outputtype)
    inputdtype = np.dtype(type(input[0]))
    input = input.astype(outputtype)
    
    input[input > 0] = input[input > 0] / np.iinfo(inputdtype).max
    input[input < 0] = input[input < 0] / -np.iinfo(inputdtype).min
    
    y = input
    return y

def nfloat_to_int(input, type=np.int16):
    """
    Convert a float array with amplitude ranging from -1 to 1 to a unnormalised signed 
    integer array with specified precision.
        
    Parameters:
        * input (type: array-like matrix of floats) - a float array. (Required)
        dtype : the output datatype (also determines the precision). 
        (Optional; Default = np.int16)
        
    Returns:
        * y (type: array-like matrix of ints) - an unnormalised to a
        range of -1 to 1.
    """
    
    input = np.array(input)
    assert input.dtype.kind == 'f', "'input' must be an array of floats!"
    
    input[input > 0] = input[ input > 0 ] * np.iinfo(type).max
    input[input < 0] = input[ input < 0 ] * -np.iinfo(type).min
    
    y = input.astype(type)
    
    return y