pyACA: Documentation 0.3.1
Source Code for Audio Content Analysis
Loading...
Searching...
No Matches
computeChords.py
Go to the documentation of this file.
1# -*- coding: utf-8 -*-
2
3import numpy as np
4
5from pyACA.computeFeature import computeFeature
6from pyACA.ToolPreprocAudio import ToolPreprocAudio
7from pyACA.ToolViterbi import ToolViterbi
8
9
10
21def computeChords(x, f_s, iBlockLength=8192, iHopLength=2048):
22
23 # chord names
24 cChords = ['C Maj', 'C# Maj', 'D Maj', 'D# Maj', 'E Maj', 'F Maj',
25 'F# Maj', 'G Maj', 'G# Maj', 'A Maj', 'A# Maj', 'B Maj',
26 'c min', 'c# min', 'd min', 'd# min', 'e min', 'f min',
27 'f# min', 'g min', 'g# min', 'a min', 'a# min', 'b min']
28
29 # chord templates
31
32 # transition probabilities
34
35 # pre-processing
36 x = ToolPreprocAudio(x, iBlockLength)
37
38 # extract pitch chroma
39 v_pc, t = computeFeature('SpectralPitchChroma', x, f_s, None, iBlockLength, iHopLength)
40
41 # estimate chord probabilities
42 P_E = np.matmul(T, v_pc)
43 P_E = P_E / np.sum(P_E, axis=0)
44
45 # allocate space for two rows of results (one raw, one with Viterbi)
46 # assign series of labels/indices starting with 0
47 aiChordIdx = np.zeros([2, len(t)]).astype(int)
48 aiChordIdx[0, :] = np.argmax(P_E, axis=0).astype(int)
49
50 # compute path with Viterbi algorithm
51 aiChordIdx[1, :], P_res = ToolViterbi(P_E, P_T, np.ones(len(cChords)) / len(cChords), True)
52
53 # assign result string
54 cChordLabel = [[cChords[i] for i in aiChordIdx[0, :]], [cChords[i] for i in aiChordIdx[1, :]]]
55
56 return cChordLabel, aiChordIdx, t, P_E
57
58
60
61 iNumRootNotes = 12
62
63 # init: 12 major and 12 minor triads
64 T = np.zeros([24, 12])
65
66 # all chord pitches are weighted equally
67 T[0, np.array([0, 4, 7])] = 1/3.
68 T[iNumRootNotes, np.array([0, 3, 7])] = 1/3.
69
70 # generate templates for all root notes
71 for i in range(1, iNumRootNotes):
72 T[i, :] = np.roll(T[0, :], i)
73 T[i+iNumRootNotes, :] = np.roll(T[iNumRootNotes, :], i)
74
75 return T
76
77
79
80 iNumRootNotes = 12
81
82 # circle of fifth tonic distances
83 circ = np.array([0, -5, 2, -3, 4, -1, 6, 1, -4, 3, -2, 5, -3, 4, -1, 6, 1, -4, 3, -2, 5, 0, -5, 2])
84
85 # set the circle radius and distance
86 R = 1
87 d = .5
88
89 # generate key coordinates (mode in z)
90 x = R * np.cos(2 * np.pi * circ/float(iNumRootNotes))
91 y = R * np.sin(2 * np.pi * circ/float(iNumRootNotes))
92 z = np.zeros(2*iNumRootNotes)
93 z[0:iNumRootNotes] = d
94
95 P_T = np.zeros([len(x), len(x)])
96
97 # compute key distances
98 for m in range(len(x)):
99 for n in range(len(x)):
100 P_T[m, n] = np.sqrt((x[m]-x[n])**2 + (y[m]-y[n])**2 + (z[m]-z[n])**2)
101
102 # convert distances into 'probabilities'
103 P_T = .1+P_T
104 P_T = 1 - P_T/(.1 + np.max(P_T))
105 P_T = P_T / np.sum(P_T, axis=0)
106
107 return P_T
108
109
110
111
114 from pyACA.ToolReadAudio import ToolReadAudio
115
116 # read audio file
117 [f_s, x] = ToolReadAudio(cPath)
118
119 # compute fingerprint
120 [cChordLabel, aiChordIdx, t, P_E] = computeChords(x, f_s)
121
122 return cChordLabel, aiChordIdx, t, P_E
123
124
125if __name__ == "__main__":
126 import argparse
127
128 # add command line args and parse them
129 parser = argparse.ArgumentParser(description='Compute chords from wav file')
130 parser.add_argument('--infile', metavar='path', required=False,
131 help='path to input audio file')
132
133 # retrieve command line args
134 args = parser.parse_args()
135 cPath = args.infile
136
137 # only for debugging
138 if __debug__:
139 if not cPath:
140 cPath = "../ACA-Plots/audio/sax_example.wav"
141
142 # call the function
143 computeChordsCl(cPath)
computeChordsCl(cPath)
main