Christophe Favergeon | e7de243 | 2021-09-07 13:28:35 +0200 | [diff] [blame] | 1 | ########################################### |
| 2 | # Project: CMSIS DSP Library |
| 3 | # Title: mfccdata.py |
| 4 | # Description: Generation of MFCC arays for the MFCC C init function |
| 5 | # |
| 6 | # $Date: 07 September 2021 |
| 7 | # $Revision: V1.10.0 |
| 8 | # |
| 9 | # Target Processor: Cortex-M and Cortex-A cores |
| 10 | # -------------------------------------------------------------------- */ |
| 11 | # |
| 12 | # Copyright (C) 2010-2021 ARM Limited or its affiliates. All rights reserved. |
| 13 | # |
| 14 | # SPDX-License-Identifier: Apache-2.0 |
| 15 | # |
| 16 | # Licensed under the Apache License, Version 2.0 (the License); you may |
| 17 | # not use this file except in compliance with the License. |
| 18 | # You may obtain a copy of the License at |
| 19 | # |
| 20 | # www.apache.org/licenses/LICENSE-2.0 |
| 21 | # |
| 22 | # Unless required by applicable law or agreed to in writing, software |
| 23 | # distributed under the License is distributed on an AS IS BASIS, WITHOUT |
| 24 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 25 | # See the License for the specific language governing permissions and |
| 26 | # limitations under the License. |
| 27 | ############################################ |
| 28 | import numpy as np |
| 29 | from jinja2 import Environment, PackageLoader, select_autoescape,FileSystemLoader |
| 30 | import os.path |
| 31 | import struct |
| 32 | import scipy.signal as sig |
| 33 | |
| 34 | def to_q31(v): |
| 35 | r = int(round(v * 2**31)) |
| 36 | if (r > 0x07FFFFFFF): |
| 37 | r = 0x07FFFFFFF |
| 38 | if (r < -0x080000000): |
| 39 | r = -0x080000000 |
| 40 | return ("0x%s" % format(struct.unpack('<I', struct.pack('<i', r))[0],'08X')) |
| 41 | |
| 42 | def to_q15(v): |
| 43 | r = int(round(v * 2**15)) |
| 44 | if (r > 0x07FFF): |
| 45 | r = 0x07FFF |
| 46 | if (r < -0x08000): |
| 47 | r = -0x08000 |
| 48 | return ("0x%s" % format(struct.unpack('<H', struct.pack('<h', r))[0],'04X')) |
| 49 | |
| 50 | def to_f16(v): |
Christophe Favergeon | 285816e | 2021-09-09 09:11:23 +0200 | [diff] [blame^] | 51 | return("(float16_t)%ff" % struct.unpack('<f',struct.pack('<f',v))) |
Christophe Favergeon | e7de243 | 2021-09-07 13:28:35 +0200 | [diff] [blame] | 52 | |
| 53 | def to_f32(v): |
| 54 | return("%ff" % struct.unpack('<f',struct.pack('<f',v))) |
| 55 | |
| 56 | class ConvertArray: |
| 57 | def __init__(self,theType): |
| 58 | self._cvt = lambda x : x |
| 59 | if theType=="f32": |
| 60 | self._cvt = to_f32 |
| 61 | if theType=="f16": |
| 62 | self._cvt = to_f16 |
| 63 | if theType=="q31": |
| 64 | self._cvt = to_q31 |
| 65 | if theType=="q15": |
| 66 | self._cvt = to_q15 |
| 67 | |
| 68 | def getArrayContent(self,samples): |
| 69 | nb = 0 |
| 70 | res="" |
| 71 | res += "{\n" |
| 72 | for sample in samples: |
| 73 | res += str(self._cvt(sample)) |
| 74 | res += "," |
| 75 | nb = nb + 1 |
| 76 | if nb == 10: |
| 77 | res += "\n" |
| 78 | nb = 0 |
| 79 | res += "}" |
| 80 | return(res) |
| 81 | |
| 82 | |
| 83 | |
| 84 | def frequencyToMelSpace(freq): |
| 85 | return 1127.0 * np.log(1.0 + freq / 700.0) |
| 86 | |
| 87 | def melSpaceToFrequency(mels): |
| 88 | return 700.0 * (np.exp(mels / 1127.0) - 1.0) |
| 89 | |
| 90 | def melFilterMatrix(fmin, fmax, numOfMelFilters,fs,FFTSize): |
| 91 | |
| 92 | filters = np.zeros((numOfMelFilters,int(FFTSize/2+1))) |
| 93 | zeros = np.zeros(int(FFTSize // 2 )) |
| 94 | |
| 95 | |
| 96 | fmin_mel = frequencyToMelSpace(fmin) |
| 97 | fmax_mel = frequencyToMelSpace(fmax) |
| 98 | mels = np.linspace(fmin_mel, fmax_mel, num=numOfMelFilters+2) |
| 99 | |
| 100 | |
| 101 | linearfreqs = np.linspace( 0, fs/2.0, int(FFTSize // 2 + 1) ) |
| 102 | spectrogrammels = frequencyToMelSpace(linearfreqs)[1:] |
| 103 | |
| 104 | |
| 105 | filtPos=[] |
| 106 | filtLen=[] |
| 107 | totalLen = 0 |
| 108 | packedFilters = [] |
| 109 | for n in range(numOfMelFilters): |
| 110 | |
| 111 | |
| 112 | upper = (spectrogrammels - mels[n])/(mels[n+1]-mels[n]) |
| 113 | lower = (mels[n+2] - spectrogrammels)/(mels[n+2]-mels[n+1]) |
| 114 | |
| 115 | |
| 116 | filters[n, :] = np.hstack([0,np.maximum(zeros,np.minimum(upper,lower))]) |
| 117 | nb = 0 |
| 118 | startFound = False |
| 119 | for sample in filters[n, :]: |
| 120 | if not startFound and sample != 0.0: |
| 121 | startFound = True |
| 122 | startPos = nb |
| 123 | |
| 124 | if startFound and sample == 0.0: |
| 125 | endPos = nb - 1 |
| 126 | break |
| 127 | nb = nb + 1 |
| 128 | filtLen.append(endPos - startPos+1) |
| 129 | totalLen += endPos - startPos + 1 |
| 130 | filtPos.append(startPos) |
| 131 | packedFilters += list(filters[n, startPos:endPos+1]) |
| 132 | |
| 133 | return filtLen,filtPos,totalLen,packedFilters,filters |
| 134 | |
| 135 | |
| 136 | def dctMatrix(numOfDctOutputs, numOfMelFilters): |
| 137 | |
| 138 | result = np.zeros((numOfDctOutputs,numOfMelFilters)) |
| 139 | s=(np.linspace(1,numOfMelFilters,numOfMelFilters) - 0.5)/numOfMelFilters |
| 140 | |
| 141 | for i in range(0, numOfDctOutputs): |
| 142 | result[i,:]=np.cos(i * np.pi*s) * np.sqrt(2.0/numOfMelFilters) |
| 143 | |
| 144 | return result |
| 145 | |
| 146 | |
| 147 | def ctype(s): |
| 148 | if s == "f64": |
| 149 | return("float64_t") |
| 150 | if s == "f32": |
| 151 | return("float32_t") |
| 152 | if s == "f16": |
| 153 | return("float16_t") |
| 154 | if s == "q31": |
| 155 | return("q31_t") |
| 156 | if s == "q15": |
| 157 | return("q15_t") |
| 158 | |
| 159 | def typeext(s): |
| 160 | if s == "f64": |
| 161 | return("_f64") |
| 162 | if s == "f32": |
| 163 | return("_f32") |
| 164 | if s == "f16": |
| 165 | return("_f16") |
| 166 | if s == "q31": |
| 167 | return("_q31") |
| 168 | if s == "q15": |
| 169 | return("_q15") |
| 170 | |
| 171 | def prepareWindowConfig(configs): |
| 172 | # sig.hamming(FFTSize, sym=False) |
| 173 | for config in configs: |
| 174 | c=configs[config] |
| 175 | if c["win"] == "hamming": |
| 176 | win = sig.hamming(c["fftlength"], sym=False) |
| 177 | if c["win"] == "hanning": |
| 178 | win = sig.hann(c["fftlength"], sym=False) |
| 179 | |
| 180 | cvt=ConvertArray(c["type"]) |
| 181 | c["ctype"]=ctype(c["type"]) |
| 182 | c["ext"]=typeext(c["type"]) |
| 183 | |
| 184 | c["winSamples"] = cvt.getArrayContent(win) |
| 185 | |
| 186 | def prepareMelconfig(configs): |
| 187 | for config in configs: |
| 188 | c=configs[config] |
| 189 | |
| 190 | cvt=ConvertArray(c["type"]) |
| 191 | cvtInt=ConvertArray(None) |
| 192 | c["ctype"]=ctype(c["type"]) |
| 193 | c["ext"]=typeext(c["type"]) |
| 194 | |
| 195 | filtLen,filtPos,totalLen,packedFilters,filters = melFilterMatrix(c["fmin"], c["fmax"], c["melFilters"],c["samplingRate"],c["fftlength"]) |
| 196 | |
| 197 | c["filtLenArray"]=cvtInt.getArrayContent(filtLen) |
| 198 | c["filtPosArray"]=cvtInt.getArrayContent(filtPos) |
| 199 | c["totalLen"]=totalLen |
| 200 | c["filters"]=cvt.getArrayContent(packedFilters) |
| 201 | |
| 202 | def prepareDctconfig(configs): |
| 203 | for config in configs: |
| 204 | c=configs[config] |
| 205 | |
| 206 | cvt=ConvertArray(c["type"]) |
| 207 | c["ctype"]=ctype(c["type"]) |
| 208 | c["ext"]=typeext(c["type"]) |
| 209 | c["dctMatrixLength"]=c["dctOutputs"] * c["melFilters"] |
| 210 | |
| 211 | dctMat = dctMatrix(c["dctOutputs"],c["melFilters"]) |
| 212 | dctMat=dctMat.reshape(c["dctMatrixLength"]) |
| 213 | c["dctMatrix"]=cvt.getArrayContent(dctMat) |
| 214 | |
| 215 | #print(configs) |
| 216 | |
Christophe Favergeon | 285816e | 2021-09-09 09:11:23 +0200 | [diff] [blame^] | 217 | def checkF16(configs): |
| 218 | hasF16 = False |
| 219 | for config in configs["dct"]: |
| 220 | c=configs["dct"][config] |
| 221 | if c["type"]=="f16": |
| 222 | hasF16 = True |
| 223 | c["hasF16"]=True |
| 224 | |
| 225 | for config in configs["melfilter"]: |
| 226 | c=configs["melfilter"][config] |
| 227 | if c["type"]=="f16": |
| 228 | hasF16 = True |
| 229 | c["hasF16"]=True |
| 230 | |
| 231 | for config in configs["window"]: |
| 232 | c=configs["window"][config] |
| 233 | if c["type"]=="f16": |
| 234 | hasF16 = True |
| 235 | c["hasF16"]=True |
| 236 | |
| 237 | configs["hasF16"]=hasF16 |
| 238 | |
Christophe Favergeon | e7de243 | 2021-09-07 13:28:35 +0200 | [diff] [blame] | 239 | env = Environment( |
| 240 | loader=PackageLoader("mfccdata","mfcctemplates"), |
| 241 | autoescape=select_autoescape(), |
| 242 | trim_blocks=True |
| 243 | ) |
| 244 | |
| 245 | ctemplate = env.get_template("mfccdata.c") |
| 246 | htemplate = env.get_template("mfccdata.h") |
| 247 | |
| 248 | |
| 249 | def genMfccHeader(f,configs,filename): |
| 250 | print(htemplate.render(configs=configs,filename=filename),file=f) |
| 251 | |
| 252 | def genMfccInit(f,configs,filename): |
| 253 | print(ctemplate.render(configs=configs,filename=filename),file=f) |