Daniel@0
|
1 # Part of DML (Digital Music Laboratory)
|
Daniel@0
|
2 # Copyright 2014-2015 Daniel Wolff, City University
|
Daniel@0
|
3
|
Daniel@0
|
4 # This program is free software; you can redistribute it and/or
|
Daniel@0
|
5 # modify it under the terms of the GNU General Public License
|
Daniel@0
|
6 # as published by the Free Software Foundation; either version 2
|
Daniel@0
|
7 # of the License, or (at your option) any later version.
|
Daniel@0
|
8 #
|
Daniel@0
|
9 # This program is distributed in the hope that it will be useful,
|
Daniel@0
|
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Daniel@0
|
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
Daniel@0
|
12 # GNU General Public License for more details.
|
Daniel@0
|
13 #
|
Daniel@0
|
14 # You should have received a copy of the GNU General Public
|
Daniel@0
|
15 # License along with this library; if not, write to the Free Software
|
Daniel@0
|
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
Daniel@0
|
17
|
Daniel@0
|
18 #!/usr/bin/python
|
Daniel@0
|
19 # -*- coding: utf-8 -*-
|
Daniel@0
|
20 #
|
Daniel@0
|
21 # This is a data conversion wrapper for the spmf toolkit.
|
Daniel@0
|
22 # The toolkit has been released under GPL3 at www.philippe-fournier-viger.com/spmf
|
Daniel@0
|
23
|
Daniel@0
|
24 __author__="Daniel Wolff"
|
Daniel@0
|
25
|
Daniel@0
|
26 import chord2function as c2f
|
Daniel@0
|
27 import csv
|
Daniel@0
|
28 import re
|
Daniel@0
|
29
|
Daniel@0
|
30 # takes a dictionary of chords for one or multiple files
|
Daniel@0
|
31 # in the form of dict[clipid] = [ (time,key,mode,fun,typ,bfun) ]
|
Daniel@0
|
32 # and converts it into spmf
|
Daniel@0
|
33 def folder2spmf(folderin = 'D:/mirg/Chord_Analysis20141216/', fileout = 'D:/mirg/Chord_Analysis20141216/Beethoven.spmf'):
|
Daniel@0
|
34
|
Daniel@0
|
35 # get chords for all files
|
Daniel@0
|
36 output = c2f.folder2functions(folderin)
|
Daniel@0
|
37
|
Daniel@0
|
38 # open log
|
Daniel@0
|
39 logfile = fileout + '.dic'
|
Daniel@0
|
40 csvfile = open(logfile, "w+b") #opens the file for updating
|
Daniel@0
|
41 w = csv.writer(csvfile)
|
Daniel@0
|
42 w.writerow(["track","key","mode","sequence length"])
|
Daniel@0
|
43
|
Daniel@0
|
44 # open spmf file
|
Daniel@0
|
45 fspmf = open(fileout,'w')
|
Daniel@0
|
46 # ---
|
Daniel@0
|
47 # this is writing the spmf format
|
Daniel@0
|
48 for track,trackdata in output.iteritems():
|
Daniel@0
|
49 # write chord sequence as one line in spmf file
|
Daniel@0
|
50 for (time,key,mode,fun,typ,bfun) in trackdata:
|
Daniel@0
|
51 chord = c2f.fun2num(fun,typ,bfun,mode)
|
Daniel@0
|
52
|
Daniel@0
|
53 # -1 is the spearator of items or itemsets
|
Daniel@0
|
54 fspmf.write(str(chord) + ' -1 ')
|
Daniel@0
|
55
|
Daniel@0
|
56 # the sequence is closed with -2
|
Daniel@0
|
57 fspmf.write('-2\n')
|
Daniel@0
|
58 w.writerow([track, str(key), str(mode),str(len(trackdata))])
|
Daniel@0
|
59
|
Daniel@0
|
60 fspmf.close()
|
Daniel@0
|
61 csvfile.close()
|
Daniel@0
|
62
|
Daniel@0
|
63 # read an spmf file
|
Daniel@0
|
64 # def parsespmf(filein = 'D:/mirg/Chord_Analysis20141216/Beethoven.txt'):
|
Daniel@0
|
65
|
Daniel@0
|
66 # string sourcefile path to the source spmf file with chords from records
|
Daniel@0
|
67 # string patternfile path to the pattern spmf file
|
Daniel@0
|
68 # matches each of the patterns in patternfile
|
Daniel@0
|
69 # to the chord sequences in sourcefile
|
Daniel@0
|
70 def match(sourcefile = 'D:/mirg/Chord_Analysis20141216/Beethoven.spmf',sourcedict = 'D:/mirg/Chord_Analysis20141216/Beethoven.spmf.dic', patternfile = 'D:/mirg/Chord_Analysis20141216/Beethoven_70.txt'):
|
Daniel@0
|
71
|
Daniel@0
|
72 # define regular expressions for matching
|
Daniel@0
|
73 # closed sequence
|
Daniel@0
|
74
|
Daniel@0
|
75 # ---
|
Daniel@0
|
76 # we here assume that there are more files than patterns,
|
Daniel@0
|
77 # as display of patterns is somehow limited
|
Daniel@0
|
78 # therefore parallelisation will be 1 pattern/multiple files
|
Daniel@0
|
79 # per instance
|
Daniel@0
|
80 # ---
|
Daniel@0
|
81
|
Daniel@0
|
82 patterns = []
|
Daniel@0
|
83 patterns_raw = []
|
Daniel@0
|
84 # read all patterns
|
Daniel@0
|
85 f = open(patternfile, 'r')
|
Daniel@0
|
86 for line in f:
|
Daniel@0
|
87 # a line looks like this:
|
Daniel@0
|
88 # 1120401 -1 1120101 -1 #SUP: 916
|
Daniel@0
|
89
|
Daniel@0
|
90
|
Daniel@0
|
91 # save pattern
|
Daniel@0
|
92 #patterns.append(pattern)
|
Daniel@0
|
93 #numeric? or just regex?
|
Daniel@0
|
94 # we'll use string, so any representation works
|
Daniel@0
|
95
|
Daniel@0
|
96 pattern,support = readPattern(line)
|
Daniel@0
|
97 patterns.append(pattern)
|
Daniel@0
|
98
|
Daniel@0
|
99 # here's the regex
|
Daniel@0
|
100 # first the spacer
|
Daniel@0
|
101 #spacer = '((\s-1\s)|((\s-1\s)*[0-9]+\s-1\s)+)'
|
Daniel@0
|
102 #repattern = r'(' + spacer + '*' + spacer.join(pattern) + spacer + '*' + '.*)'
|
Daniel@0
|
103 #print repattern
|
Daniel@0
|
104 #patterns.append(re.compile(repattern))
|
Daniel@0
|
105
|
Daniel@0
|
106 # ---
|
Daniel@0
|
107 # now for the input sequences
|
Daniel@0
|
108 # ---
|
Daniel@0
|
109 # first: read track dictionary and get the input sequence names
|
Daniel@0
|
110 tracks = getClipDict(sourcedict)
|
Daniel@0
|
111
|
Daniel@0
|
112 # read the input sequences
|
Daniel@0
|
113 source = open(sourcefile, 'r')
|
Daniel@0
|
114 patterns_tracks = dict()
|
Daniel@0
|
115 tracks_patterns = dict()
|
Daniel@0
|
116
|
Daniel@0
|
117 # iterate over all tracks - to be parallelised
|
Daniel@0
|
118 for track,count in tracks.iteritems():
|
Daniel@0
|
119 sequence = readSequence(next(source))
|
Daniel@0
|
120 print track
|
Daniel@0
|
121 for p in range(0,len(patterns)):
|
Daniel@0
|
122 # match open or closed pattern
|
Daniel@0
|
123 if openPatternInSequence(sequence,patterns[p]):
|
Daniel@0
|
124 if patterns_tracks.has_key(p):
|
Daniel@0
|
125 patterns_tracks[p].append(track)
|
Daniel@0
|
126 else:
|
Daniel@0
|
127 patterns_tracks[p] = [track]
|
Daniel@0
|
128
|
Daniel@0
|
129 if tracks_patterns.has_key(track):
|
Daniel@0
|
130 tracks_patterns[track].append(p)
|
Daniel@0
|
131 else:
|
Daniel@0
|
132 tracks_patterns[track] = [p]
|
Daniel@0
|
133
|
Daniel@0
|
134 # write clip index to files
|
Daniel@0
|
135 writeAllPatternsForClips('D:/mirg/Chord_Analysis20141216/',tracks_patterns)
|
Daniel@0
|
136 #print patterns_tracks[p]
|
Daniel@0
|
137
|
Daniel@0
|
138 # writes results to disk per key
|
Daniel@0
|
139 def writeAllPatternsForClips(path = 'D:/mirg/Chord_Analysis20141216/',tracks_patterns = dict()):
|
Daniel@0
|
140
|
Daniel@0
|
141 for name, contents in tracks_patterns.iteritems():
|
Daniel@0
|
142 # create new file
|
Daniel@0
|
143 csvfile = open(path + '/' + name + '_patterns.csv', "w+b") #opens the file for updating
|
Daniel@0
|
144 w = csv.writer(csvfile)
|
Daniel@0
|
145
|
Daniel@0
|
146 # compress pattern data ?
|
Daniel@0
|
147 # e.g. 2 columns from-to for the long series of atomic increments
|
Daniel@0
|
148
|
Daniel@0
|
149 w.writerow(contents)
|
Daniel@0
|
150 csvfile.close()
|
Daniel@0
|
151
|
Daniel@0
|
152
|
Daniel@0
|
153 # @param line: reads a line in the spmf output file with frequent patterns
|
Daniel@0
|
154 # returns list of strings "pattern" and int "support"
|
Daniel@0
|
155 def readPattern(line):
|
Daniel@0
|
156 # locate support
|
Daniel@0
|
157 suploc = line.find('#SUP:')
|
Daniel@0
|
158 support = int(line[suploc+5:-1])
|
Daniel@0
|
159
|
Daniel@0
|
160 # extract pattern
|
Daniel@0
|
161 pattern = line[:suploc].split(' -1 ')[:-1]
|
Daniel@0
|
162 return (pattern,support)
|
Daniel@0
|
163
|
Daniel@0
|
164 # @param line: reads a line in the spmf input file with chord sequence
|
Daniel@0
|
165 # returns list of strings "pattern" and int "support"
|
Daniel@0
|
166 def readSequence(line):
|
Daniel@0
|
167 # locate support
|
Daniel@0
|
168 suploc = line.find('-2')
|
Daniel@0
|
169
|
Daniel@0
|
170 # extract pattern
|
Daniel@0
|
171 sequence = line[:suploc].split(' -1 ')[:-1]
|
Daniel@0
|
172 return sequence
|
Daniel@0
|
173
|
Daniel@0
|
174 # finds open pattern in sequences
|
Daniel@0
|
175 # @param [string] sequence input sequence
|
Daniel@0
|
176 # @param [string] pattern pattern to be found
|
Daniel@0
|
177 def openPatternInSequence(sequence,pattern):
|
Daniel@0
|
178 patidx = 0
|
Daniel@0
|
179 for item in sequence:
|
Daniel@0
|
180 if item == pattern[patidx]:
|
Daniel@0
|
181 patidx +=1
|
Daniel@0
|
182
|
Daniel@0
|
183 # did we complet the pattern?
|
Daniel@0
|
184 if patidx >= (len(pattern)-1):
|
Daniel@0
|
185 # could also return the start index
|
Daniel@0
|
186 return 1
|
Daniel@0
|
187 # finished the sequence before finishing pattern
|
Daniel@0
|
188 return 0
|
Daniel@0
|
189
|
Daniel@0
|
190 # finds closed pattern in sequences
|
Daniel@0
|
191 # @param [string] sequence input sequence
|
Daniel@0
|
192 # @param [string] pattern pattern to be found
|
Daniel@0
|
193 def closedPatternInSequence(sequence,pattern):
|
Daniel@0
|
194 # alternatively use KnuthMorrisPratt with unsplit string
|
Daniel@0
|
195 return ''.join(map(str, pattern)) in ''.join(map(str, sequence))
|
Daniel@0
|
196
|
Daniel@0
|
197 # reads all track names from the dictionary created by folder2spmf
|
Daniel@0
|
198 # @param sourcedict path to dictionary
|
Daniel@0
|
199 def getClipDict(sourcedict):
|
Daniel@0
|
200
|
Daniel@0
|
201 f = open(sourcedict, 'rt')
|
Daniel@0
|
202 reader = csv.reader(f)
|
Daniel@0
|
203
|
Daniel@0
|
204 # skip first roow that contains legend
|
Daniel@0
|
205 next(reader)
|
Daniel@0
|
206
|
Daniel@0
|
207 # get following rows
|
Daniel@0
|
208 tracks = dict()
|
Daniel@0
|
209 for (track,key,mode,seqlen) in reader:
|
Daniel@0
|
210 tracks[track]= (key,mode,seqlen)
|
Daniel@0
|
211 #tracks.append((track,count))
|
Daniel@0
|
212
|
Daniel@0
|
213 f.close()
|
Daniel@0
|
214 return tracks
|
Daniel@0
|
215
|
Daniel@0
|
216
|
Daniel@0
|
217 # run spmf afterwards with java -jar spmf.jar run CM-SPADE Beethoven.spmf output.txt 50% 3
|
Daniel@0
|
218 if __name__ == "__main__":
|
Daniel@0
|
219 #folder2spmf()
|
Daniel@0
|
220 match() |