changeset 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 0ee61aeb789a
children ef62cca13f36
files cortical_model.py loudnessRatios.py run.py runScript1.py testELC.py utils.py
diffstat 6 files changed, 389 insertions(+), 34 deletions(-) [+]
line wrap: on
line diff
--- a/cortical_model.py	Mon Feb 24 15:31:15 2014 +0000
+++ b/cortical_model.py	Mon Mar 31 19:22:23 2014 +0100
@@ -14,7 +14,6 @@
 External dependencies:
     > scipy
     > numpy
-    > copy
     > matplotlib
     
 Primary functions:
@@ -33,7 +32,6 @@
 import scipy.signal as sp
 import scipy.fftpack as fft
 import numpy as np
-from copy import deepcopy
 import matplotlib.pyplot as plt
 
 def calc_loudness_noise(input, fs, SPL, rectify=False, inc_loud_region = False, verbose = False):
@@ -48,7 +46,7 @@
     sl = get_specific_loudness_noise(exc_i, inc_loud_region = inc_loud_region)
     loudness = 2*np.sum(sl,1)
 
-    return loudness
+    return sl, loudness
 
 def calc_loudness_quiet(input, fs, SPL, rectify = False, inc_loud_region = False, verbose = False):
     
@@ -57,7 +55,7 @@
     
     loudness = 2*np.sum(sl,0)
     
-    return loudness
+    return sl, loudness
 
 def get_specific_loudness_noise(exc_i, analyse = "all", inc_loud_region = False):
     """
@@ -86,21 +84,19 @@
         exc_noise = exc_signoise - exc_sig
         tn = K*exc_noise + tq
 
-        less_than_tq = get_less_than_tq(exc_sig, tn) #boolean array of elements less than or equal to the threshold of quiet
-        greater_than_tq = ~less_than_tq #boolean array of elements greater than the threshold of quiet
+        less_than_tn = get_less_than_tq(exc_sig, tn) #boolean array of elements less than or equal to the threshold of quiet
+        greater_than_tn = ~less_than_tn #boolean array of elements greater than the threshold of quiet
         
         if inc_loud_region:
-            
             c1 = signoise_lt_tl * greater_than_tn #case 1
             c2 = signoise_lt_tl * less_than_tn #case 2
             c3 = signoise_gt_tl * greater_than_tn #case 3
-            c4 = signoise_gt_tl * less_than_tn
+            c4 = signoise_gt_tl * less_than_tn #case 4
             specific_loudness[i, c3] = get_specific_loudness_noise_c3(exc_sig[c3], exc_noise[c3], tq[c3], tn[c3], G[c3], A[c3], alpha[c3])
             specific_loudness[i, c4] = get_specific_loudness_noise_c4(exc_sig[c4], exc_noise[c4], tq[c4], tn[c4], G[c4], A[c4], alpha[c4])
-            
         else:
-            c1 = greater_than_tq #case 1
-            c2 = less_than_tq #case 2
+            c1 = greater_than_tn #case 1
+            c2 = less_than_tn #case 2
 
         specific_loudness[i][c1] = get_specific_loudness_noise_c1(exc_sig[c1], exc_noise[c1], tq[c1], tn[c1], G[c1], A[c1], alpha[c1])
         specific_loudness[i][c2] = get_specific_loudness_noise_c2(exc_sig[c2], exc_noise[c2], tq[c2], tn[c2], G[c2], A[c2], alpha[c2])
@@ -165,7 +161,7 @@
     less_than_tq = get_less_than_tq(exc_i, tq) #boolean array of elements less than or equal to the threshold of quiet
     greater_than_tq = ~less_than_tq #boolean array of elements greater than the threshold of quiet
     greater_than_tl = get_greater_than_tl(exc_i) #boolean array of elements greater than the loud threshold
-    gttq_lttl = greater_than_tq[~greater_than_tl] #boolean array of elements greater than the threshold of quiet but less than the threshold of loud
+    gttq_lttl = greater_than_tq * ~greater_than_tl #boolean array of elements greater than the threshold of quiet but less than the threshold of loud
     
     exc_low = exc_i[less_than_tq]
     specific_loudness[less_than_tq] = get_specific_loudness_low(exc_low, G[less_than_tq], tq[less_than_tq], A[less_than_tq], alpha[less_than_tq])
@@ -218,6 +214,7 @@
     fkRef = 10**np.append(np.append(np.array(1), np.linspace(np.log10(50), np.log10(500), 11)), np.linspace(3,4.5,16))
     kRef = np.append(np.append(13, np.linspace(13,-3,11)), np.ones((1,16))*-3)
     K = np.interp(fc,fkRef,kRef)
+    K = 10**(K/10)
     K = np.transpose(np.tile(K, (lenSig,1)))
 
     return K
@@ -658,8 +655,8 @@
         if(verbose):
             sys.stdout.write("\r" + str(int(np.round(100.0*i/nFilters))) + "% complete.")
             sys.stdout.flush()
-        x = deepcopy(input)
-        y[i] = sp.lfilter(b[i],a[i],x)
+        x = np.array(input)
+        y[i] = sp.fftconvolve(x,b[i],'same')
 
     if(verbose): sys.stdout.write("\n")
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/loudnessRatios.py	Mon Mar 31 19:22:23 2014 +0100
@@ -0,0 +1,147 @@
+import cortical_model as cm
+import utils
+import numpy as np
+from os import chdir
+from os import listdir
+import matplotlib.pyplot as plt
+
+chdir("Stimuli")
+dirList = listdir(".")
+
+if(dirList[0] == ".DS_Store"):
+    dirList.pop(0)
+
+numEntries = len(dirList)
+ratio = np.zeros((3, numEntries))
+jazzRatio = np.zeros((3, 6))
+rockRatio = np.zeros((3, 5))
+funkRatio = np.zeros((3, 6))
+
+i = 0
+jazzi = 0
+funki = 0
+rocki = 0
+
+#get ratios
+for dir in dirList:
+    chdir(dir)
+    loudness = np.load("loudness.npy")
+    maxHat = loudness[0].max()
+    maxKick = loudness[1].max()
+    maxSnare = loudness[2].max()
+
+    ratio[0][i] = maxHat/maxKick
+    ratio[1][i] = maxHat/maxSnare
+    ratio[2][i] = maxKick/maxSnare
+    
+    if(dir.find("jazz") != -1):
+        jazzRatio[0][jazzi] = ratio[0][i]
+        jazzRatio[1][jazzi] = ratio[1][i]
+        jazzRatio[2][jazzi] = ratio[2][i]
+        jazzi = jazzi + 1
+    elif(dir.find("funk") != -1):
+        funkRatio[0][funki] = ratio[0][i]
+        funkRatio[1][funki] = ratio[1][i]
+        funkRatio[2][funki] = ratio[2][i]
+        funki = funki + 1
+    elif(dir.find("rock") != -1):
+        rockRatio[0][rocki] = ratio[0][i]
+        rockRatio[1][rocki] = ratio[1][i]
+        rockRatio[2][rocki] = ratio[2][i]
+        rocki = rocki + 1
+
+    chdir("../")
+
+    i=i+1
+
+chdir("..")
+
+def plot_all():
+    #calculate descriptive statistics
+    meanRatio = np.mean(ratio,1)
+    stdRatio = np.std(ratio,1)
+    seRatio = stdRatio/np.sqrt(numEntries)
+    rangeRatio = ratio.max(1)-ratio.min(1)
+
+    fig = plt.figure()
+    ax = fig.add_subplot(111)
+    ax.plot(meanRatio, 'xk')
+    ax.plot(meanRatio+stdRatio, '_b')
+    ax.plot(meanRatio-stdRatio, '_b')
+    ax.plot(meanRatio+seRatio, '_r')
+    ax.plot(meanRatio-seRatio, '_r')
+    #ax.plot(ratio, '.k')
+    plt.xlim([-1,3])
+    plt.xticks(np.array(range(5))-1,['','Hat:Kick','Hat:Snare','Kick:Snare',''])
+    plt.xlabel('Stimuli')
+    plt.ylabel('Loudness Ratio')
+
+    plt.show()
+
+def plot_indiv():
+    
+    #calculate descriptive statistics
+    meanJazz = np.mean(jazzRatio,1)
+    meanRock = np.mean(rockRatio,1)
+    meanFunk = np.mean(funkRatio,1)
+    stdJazz = np.std(jazzRatio,1)
+    stdRock = np.std(jazzRatio,1)
+    stdFunk = np.std(jazzRatio,1)
+    seJazz = stdJazz/np.sqrt(6)
+    seRock = stdRock/np.sqrt(5)
+    seFunk = stdFunk/np.sqrt(6)
+    #rangeRatio = ratio.max(1)-ratio.min(1)
+    
+    fig = plt.figure()
+    ax = fig.add_subplot(111)
+    ax.plot(meanJazz, 'or')
+    ax.plot(meanRock, 'og')
+    ax.plot(meanFunk, 'ob')
+    ax.legend(('Jazz', 'Rock', 'Funk'))
+    ax.plot(meanJazz+stdJazz, '_r')
+    ax.plot(meanRock+stdRock, '_g')
+    ax.plot(meanFunk+stdFunk, '_b')
+    ax.plot(meanJazz-stdJazz, '_r')
+    ax.plot(meanRock-stdRock, '_g')
+    ax.plot(meanFunk-stdFunk, '_b')
+    ax.plot(meanJazz+seJazz, '+r')
+    ax.plot(meanRock+seRock, '+g')
+    ax.plot(meanFunk+seFunk, '+b')
+    ax.plot(meanJazz-seJazz, '+r')
+    ax.plot(meanRock-seRock, '+g')
+    ax.plot(meanFunk-seFunk, '+b')
+    #ax.plot(ratio, '.k')
+    plt.xlim([-1,3])
+    plt.xticks(np.array(range(5))-1,['','Hat:Kick','Hat:Snare','Kick:Snare',''])
+    plt.xlabel('Stimuli')
+    plt.ylabel('Loudness Ratio')
+    
+    plt.show()
+
+plot_indiv()
+del dirList, maxHat, maxKick, maxSnare
+
+#plt.show()
+
+#chdir("../Stimuli2")
+#
+#for dir in dirList:
+#    chdir(dir)
+#    loudness = np.load("loudness.npy")
+#    maxOH = loudness[0].max()
+#    maxKick = loudness[1].max()
+#    maxSnare = loudness[2].max()
+#
+#    ratio = np.zeros((3))
+#    ratio[0] = maxOH/maxKick
+#    ratio[1] = maxOH/maxSnare
+#    ratio[2] = maxKick/maxSnare
+#
+#    if(ratio[0] > 16):
+#        print dir
+#
+#    np.save("ratio", ratio)
+#
+#    plt.plot(ratio, 'or')
+#
+#    chdir("../")
\ No newline at end of file
--- a/run.py	Mon Feb 24 15:31:15 2014 +0000
+++ b/run.py	Mon Mar 31 19:22:23 2014 +0100
@@ -2,17 +2,16 @@
 import numpy as np
 import utils
 
-t = np.array(range(44100))
-env = np.ones((44100))
-#T = int(np.floor(0.02*44100))
-#tRamp = np.array(range(T))
-#env[tRamp] = np.sin(2*np.pi*12.5*tRamp/44100)**2
-#env[-tRamp] = np.sin(2*np.pi*12.5*tRamp/44100)**2
+fs,hat = utils.wavread2('hat.wav')
+hat = sum(hat,0)
+fs,kick = utils.wavread2('kick.wav')
+kick = sum(kick,0)
+fs,snare = utils.wavread2('snare.wav')
+snare = sum(snare,0)
 
-sine = np.sin(2*np.pi*t*1000/44100) * env
-sine1 = np.sin(2*np.pi*t*100/44100) * env
-x = np.append(sine, sine1)
+drums = np.zeros((3,len(hat)))
+drums[0]=hat
+drums[1]=kick
+drums[2]=snare
 
-loudness = cm.calc_loudness_quiet(x,44100,90)
-
-utils.plot_waveform(loudness)
\ No newline at end of file
+loudness = cm.calc_loudness_noise(drums,fs,90, inc_loud_region = True)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runScript1.py	Mon Mar 31 19:22:23 2014 +0100
@@ -0,0 +1,62 @@
+import os
+import utils
+import cortical_model as cm
+import numpy as np
+
+os.chdir("Stimuli")
+dirList = os.listdir('.')
+if dirList[0] == '.DS_Store':
+    dirList.pop(0)
+
+print "Set 1"
+
+for dir in dirList:
+    print dir
+    
+    os.chdir(dir)
+    fs,hat = utils.wavread2('hat.wav')
+    fs,kick = utils.wavread2('kick.wav')
+    fs,snare = utils.wavread2('snare.wav')
+
+    drums = np.zeros((3,len(hat)))
+    drums[0]=hat
+    drums[1]=kick
+    drums[2]=snare
+    exc = np.zeros((3,39,len(hat)))
+
+    for i in range(3):
+        exc[i] = cm.get_excitation_i(drums[i],fs,90)
+
+    np.save('exc', exc)
+
+    os.chdir('..')
+
+kick = 0
+snare = 0
+hat = 0
+
+print "Set 2"
+
+os.chdir("../Stimuli2")
+
+for dir in dirList:
+    print dir
+    
+    os.chdir(dir)
+    fs,oh = utils.wavread2('oh.wav')
+    fs,kick = utils.wavread2('kick.wav')
+    fs,snare = utils.wavread2('snare.wav')
+    
+    drums = np.zeros((3,len(oh)))
+    drums[0]=oh
+    drums[1]=kick
+    drums[2]=snare
+    
+    exc = np.zeros((3,39,len(oh)))
+    
+    for i in range(3):
+        exc[i] = cm.get_excitation_i(drums[i],fs,90)
+    
+    np.save('exc', exc)
+    
+    os.chdir('..')
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/testELC.py	Mon Mar 31 19:22:23 2014 +0100
@@ -0,0 +1,91 @@
+import cortical_model as cm
+import numpy as np
+import matplotlib.pyplot as plt
+import pdb
+import scipy.optimize as opt
+import scipy.interpolate as interp
+
+fArray = [20, 50, 100, 200, 400, 800, 1000, 2000, 3000, 4000, 5000, 6000, 10000, 12000, 15000]
+refLoudness = None
+refDB = None
+env = np.ones((44100))
+T = int(np.floor(0.02*44100))
+tRamp = np.array(range(T))
+env[tRamp] = np.sin(2*np.pi*12.5*tRamp/44100)**2
+env[-tRamp] = np.sin(2*np.pi*12.5*tRamp/44100)**2
+
+def get_elc_range(verbose=False):
+    levels = range(10, 120, 10)
+    elc = np.zeros((len(levels), len(fArray)))
+    
+    for i in range(len(levels)):
+        if(verbose):
+            print "Level: " + str(levels[i])
+        elc[i] = get_elc(levels[i], verbose=verbose)
+        plot_elc(elc[i], show=False)
+
+    plt.show()
+
+    return elc
+
+def get_elc(SPL = 90, verbose=False):
+    fs = 44100
+    t = np.array(range(fs))
+    elc = np.zeros((len(fArray)))
+    set_refLoudness(fs, SPL)
+    
+    for i in range(len(fArray)):
+        if (fArray[i] == 1000):
+            elc[i] = SPL
+        else:
+            if(verbose):
+                print fArray[i]
+            input = np.sin(fArray[i] * t * 2 * np.pi / fs) * env
+            elc[i] = get_el(input, fs, refLoudness, SPL, verbose=verbose)
+
+    return elc
+
+def get_el(input, fs, refL, SPL = 90, verbose=False):
+    
+    x = opt.fsolve(getLoudnessDifference, SPL, (input, fs, refL, verbose), factor=0.1, xtol=0.00005)
+    
+    return x
+
+def getLoudnessDifference(SPL, input, fs, refL, verbose=False):
+
+    if(verbose):
+        print "SPL: ", SPL
+    compLoudness = rms(cm.calc_loudness_quiet(input, fs, SPL, rectify=False, inc_loud_region=True)[1])
+    difference = np.abs(refL - compLoudness)
+    if(verbose):
+        print "Difference: ", difference
+
+    return difference
+
+def rms(input):
+
+    output = np.sqrt(np.mean(input**2))
+
+    return output
+
+def set_refLoudness(fs, SPL = 90):
+    refDB = SPL
+    t = np.array(range(fs))
+
+    global refLoudness
+    input = np.sin(1000 * t * 2 * np.pi / fs) * env
+    refLoudness = rms(cm.calc_loudness_quiet(input, fs, SPL, rectify=False, inc_loud_region=True)[1])
+
+    return
+
+def plot_elc(cmOut, show = True):
+
+    x = np.array(range(min(fArray),max(fArray),5))
+    values = interp.interp1d(fArray, cmOut)
+    plt.gca().set_xscale('log')
+    plt.ylim((0,170))
+    plt.plot(fArray, cmOut, 'x', x, values(x))
+    if(show):
+        plt.show()
+
+    return
\ No newline at end of file
--- a/utils.py	Mon Feb 24 15:31:15 2014 +0000
+++ b/utils.py	Mon Mar 31 19:22:23 2014 +0100
@@ -13,9 +13,21 @@
 """
 
 import numpy as np
-import scipy.io.wavfile as wave
+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():
 	"""
@@ -34,7 +46,7 @@
         * erb.dat (place in the same folder as utils.pyc)
 	"""
 	# read data from a text file
-	data=np.loadtxt("erb.dat",delimiter=",")
+	data=np.loadtxt(gPath + "/erb.dat",delimiter=",")
 
 	# load centre frequencies from the first column into fc
 	fc=np.array(data[:,0])
@@ -58,7 +70,7 @@
         * outMidFir.dat (place in the same folder as utils.pyc)
     """
 
-    b=np.array(np.loadtxt("outMidFir.dat", delimiter=","))
+    b=np.array(np.loadtxt(gPath + "/outMidFir.dat", delimiter=","))
 
     return b
 
@@ -101,7 +113,7 @@
         * tq.dat (place in the same folder as utils.pyc)
     """
 
-    tq = np.array(np.loadtxt("tq_dB.dat",delimiter=","))
+    tq = np.array(np.loadtxt(gPath + "/tq_dB.dat",delimiter=","))
 
     return tq
 
@@ -119,7 +131,7 @@
         * A.dat (place in the same folder as utils.pyc)
     """
     
-    A = np.array(np.loadtxt("A.dat",delimiter=","))
+    A = np.array(np.loadtxt(gPath + "/A.dat",delimiter=","))
     
     return A
 
@@ -137,7 +149,7 @@
         * alpha.dat (place in the same folder as utils.pyc)
     """
     
-    alpha = np.array(np.loadtxt("alpha.dat",delimiter=","))
+    alpha = np.array(np.loadtxt(gPath + "/alpha.dat",delimiter=","))
     
     return alpha
 
@@ -176,11 +188,58 @@
             to an amplitude range of -1 to 1.
     """
 
-    fs,data = wave.read(file)
+    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 
@@ -213,7 +272,7 @@
         return
 
     data = nfloat_to_int(data)
-    wave.write(file, fs, data)
+    wav.write(file, fs, data)
     
     return