-
Notifications
You must be signed in to change notification settings - Fork 27
/
Copy pathchromagram.py
107 lines (79 loc) · 2.9 KB
/
chromagram.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
"""
Algorithm based on the paper 'Automatic Chord Recognition from
Audio Using Enhanced Pitch Class Profile' by Kyogu Lee
This script computes 12 dimensional chromagram for chord detection
@author ORCHISAMA
"""
from __future__ import division
from scipy.signal import hamming
from scipy.fftpack import fft
import numpy as np
import matplotlib.pyplot as plt
def nearestPow2(inp):
power = np.ceil(np.log2(inp))
return 2**power
"""Function to calculcate Harmonic Power Spectrum from DFT"""
def HPS(dft, M):
hps_len = int(np.ceil(np.size(dft) / (2**M)))
hps = np.ones(hps_len)
for n in range(hps_len):
for m in range(M + 1):
hps[n] *= np.absolute(dft[(2**m) * n])
return hps
"""Function to compute CQT using sparse matrix multiplication, Brown and Puckette 1992- fast"""
def CQT_fast(x, fs, bins, fmin, fmax, M):
threshold = 0.0054 # for Hamming window
K = int(bins * np.ceil(np.log2(fmax / fmin)))
Q = 1 / (2 ** (1 / bins) - 1)
nfft = np.int32(nearestPow2(np.ceil(Q * fs / fmin)))
tempKernel = np.zeros(nfft, dtype=np.complex)
specKernel = np.zeros(nfft, dtype=np.complex)
sparKernel = []
# create sparse Kernel
for k in range(K - 1, -1, -1):
fk = (2 ** (k / bins)) * fmin
N = np.int32(np.round((Q * fs) / fk))
tempKernel[:N] = hamming(N) / N * np.exp(-2 * np.pi * 1j * Q * np.arange(N) / N)
specKernel = fft(tempKernel)
specKernel[np.where(np.abs(specKernel) <= threshold)] = 0
if k == K - 1:
sparKernel = specKernel
else:
sparKernel = np.vstack((specKernel, sparKernel))
sparKernel = np.transpose(np.conjugate(sparKernel)) / nfft
ft = fft(x, nfft)
cqt = np.dot(ft, sparKernel)
ft = fft(x, nfft * (2**M))
# calculate harmonic power spectrum
# harm_pow = HPS(ft,M)
# cqt = np.dot(harm_pow, sparKernel)
return cqt
"""Function to compute constant Q Transform, Judith Brown, 1991 - slow"""
def CQT_slow(x, fs, bins, fmin, fmax):
K = int(bins * np.ceil(np.log2(fmax / fmin)))
Q = 1 / (2 ** (1 / bins) - 1)
cqt = np.zeros(K, dtype=np.complex)
for k in range(K):
fk = (2 ** (k / bins)) * fmin
N = int(np.round(Q * fs / fk))
arr = -2 * np.pi * 1j * Q * np.arange(N) / N
cqt[k] = np.dot(x[:N], np.transpose(hamming(N) * np.exp(arr))) / N
return cqt
"""Function to compute Pitch Class Profile from constant Q transform"""
def PCP(cqt, bins, M):
CH = np.zeros(bins)
for b in range(bins):
CH[b] = np.sum(cqt[b + (np.arange(M) * bins)])
return CH
def compute_chroma(x, fs):
fmin = 96
fmax = 5250
bins = 12
M = 3
nOctave = np.int32(np.ceil(np.log2(fmax / fmin)))
CH = np.zeros(bins)
# Compute constant Q transform
cqt_fast = CQT_fast(x, fs, bins, fmin, fmax, M)
# get Pitch Class Profile
CH = PCP(np.absolute(cqt_fast), bins, nOctave)
return CH