Chris@37
|
1 /*
|
Chris@37
|
2 jVamp
|
Chris@37
|
3
|
Chris@37
|
4 A Java host interface for Vamp audio analysis plugins
|
Chris@37
|
5
|
Chris@37
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@37
|
7 Copyright 2012 Chris Cannam and QMUL.
|
Chris@37
|
8
|
Chris@37
|
9 Permission is hereby granted, free of charge, to any person
|
Chris@37
|
10 obtaining a copy of this software and associated documentation
|
Chris@37
|
11 files (the "Software"), to deal in the Software without
|
Chris@37
|
12 restriction, including without limitation the rights to use, copy,
|
Chris@37
|
13 modify, merge, publish, distribute, sublicense, and/or sell copies
|
Chris@37
|
14 of the Software, and to permit persons to whom the Software is
|
Chris@37
|
15 furnished to do so, subject to the following conditions:
|
Chris@37
|
16
|
Chris@37
|
17 The above copyright notice and this permission notice shall be
|
Chris@37
|
18 included in all copies or substantial portions of the Software.
|
Chris@37
|
19
|
Chris@37
|
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
Chris@37
|
21 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
Chris@37
|
22 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
Chris@37
|
23 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
|
Chris@37
|
24 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
Chris@37
|
25 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
Chris@37
|
26 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
Chris@37
|
27
|
Chris@37
|
28 Except as contained in this notice, the names of the Centre for
|
Chris@37
|
29 Digital Music; Queen Mary, University of London; and Chris Cannam
|
Chris@37
|
30 shall not be used in advertising or otherwise to promote the sale,
|
Chris@37
|
31 use or other dealings in this Software without prior written
|
Chris@37
|
32 authorization.
|
Chris@37
|
33 */
|
Chris@31
|
34
|
Chris@43
|
35 import java.util.List;
|
Chris@31
|
36 import java.util.TreeMap;
|
Chris@31
|
37 import java.util.Map;
|
Chris@31
|
38 import java.util.List;
|
Chris@31
|
39 import java.lang.RuntimeException;
|
Chris@31
|
40
|
Chris@31
|
41 import org.vamp_plugins.PluginLoader;
|
Chris@31
|
42 import org.vamp_plugins.Plugin;
|
Chris@31
|
43 import org.vamp_plugins.ParameterDescriptor;
|
Chris@31
|
44 import org.vamp_plugins.OutputDescriptor;
|
Chris@31
|
45 import org.vamp_plugins.Feature;
|
Chris@31
|
46 import org.vamp_plugins.RealTime;
|
Chris@31
|
47
|
Chris@31
|
48 import javax.sound.sampled.AudioSystem;
|
Chris@31
|
49 import javax.sound.sampled.AudioInputStream;
|
Chris@31
|
50 import javax.sound.sampled.AudioFormat;
|
Chris@31
|
51 import javax.sound.sampled.UnsupportedAudioFileException;
|
Chris@31
|
52
|
Chris@31
|
53 import java.io.File;
|
Chris@31
|
54 import java.io.IOException;
|
Chris@31
|
55
|
Chris@31
|
56 public class host
|
Chris@31
|
57 {
|
Chris@31
|
58 private static void printFeatures(RealTime frameTime, Integer output,
|
Chris@43
|
59 Map<Integer, List<Feature>> features)
|
Chris@31
|
60 {
|
Chris@31
|
61 if (!features.containsKey(output)) return;
|
Chris@31
|
62
|
Chris@31
|
63 for (Feature f : features.get(output)) {
|
Chris@31
|
64 if (f.hasTimestamp) {
|
Chris@31
|
65 System.out.print(f.timestamp);
|
Chris@31
|
66 } else {
|
Chris@31
|
67 System.out.print(frameTime);
|
Chris@31
|
68 }
|
Chris@31
|
69 if (f.hasDuration) {
|
Chris@31
|
70 System.out.print("," + f.duration);
|
Chris@31
|
71 }
|
Chris@31
|
72 System.out.print(":");
|
Chris@31
|
73 for (float v : f.values) {
|
Chris@31
|
74 System.out.print(" " + v);
|
Chris@31
|
75 }
|
Chris@31
|
76 System.out.print(" " + f.label);
|
Chris@31
|
77 System.out.println("");
|
Chris@31
|
78 }
|
Chris@31
|
79 }
|
Chris@31
|
80
|
Chris@31
|
81 private static void usage() {
|
Chris@31
|
82 System.err.println("Usage: host pluginlibrary:plugin:output file.wav");
|
Chris@31
|
83 }
|
Chris@31
|
84
|
Chris@31
|
85 private static int readBlock(AudioFormat format, AudioInputStream stream,
|
Chris@31
|
86 float[][] buffers)
|
Chris@31
|
87 throws java.io.IOException
|
Chris@31
|
88 {
|
Chris@31
|
89 // 16-bit LE signed PCM only
|
Chris@31
|
90 int channels = format.getChannels();
|
Chris@31
|
91 byte[] raw = new byte[buffers[0].length * channels * 2];
|
Chris@31
|
92 int read = stream.read(raw);
|
Chris@31
|
93 if (read < 0) return read;
|
Chris@31
|
94 int frames = read / (channels * 2);
|
Chris@31
|
95 for (int i = 0; i < frames; ++i) {
|
Chris@31
|
96 for (int c = 0; c < channels; ++c) {
|
Chris@31
|
97 int ix = i * channels + c;
|
Chris@31
|
98 int ival = (raw[ix*2] & 0xff) | (raw[ix*2 + 1] << 8);
|
Chris@31
|
99 float fval = ival / 32768.0f;
|
Chris@31
|
100 buffers[c][i] = fval;
|
Chris@31
|
101 }
|
Chris@31
|
102 }
|
Chris@31
|
103 return frames;
|
Chris@31
|
104 }
|
Chris@31
|
105
|
Chris@31
|
106 public static void main(String[] args)
|
Chris@31
|
107 {
|
Chris@31
|
108 if (args.length < 2) {
|
Chris@31
|
109 usage();
|
Chris@31
|
110 return;
|
Chris@31
|
111 }
|
Chris@31
|
112
|
Chris@31
|
113 PluginLoader loader = PluginLoader.getInstance();
|
Chris@31
|
114
|
Chris@31
|
115 String key = args[0];
|
Chris@31
|
116 String filename = args[1];
|
Chris@31
|
117
|
Chris@31
|
118 String[] keyparts = key.split(":");
|
Chris@31
|
119 if (keyparts.length < 3) {
|
Chris@31
|
120 usage();
|
Chris@31
|
121 return;
|
Chris@31
|
122 }
|
Chris@31
|
123
|
Chris@31
|
124 String pluginKey = keyparts[0] + ":" + keyparts[1];
|
Chris@31
|
125 String outputKey = keyparts[2];
|
Chris@31
|
126
|
Chris@31
|
127 try {
|
Chris@31
|
128 File f = new File(filename);
|
Chris@31
|
129 AudioInputStream stream = AudioSystem.getAudioInputStream(f);
|
Chris@31
|
130 AudioFormat format = stream.getFormat();
|
Chris@31
|
131
|
Chris@31
|
132 if (format.getSampleSizeInBits() != 16 ||
|
Chris@31
|
133 format.getEncoding() != AudioFormat.Encoding.PCM_SIGNED ||
|
Chris@31
|
134 format.isBigEndian()) {
|
Chris@31
|
135 System.err.println("Sorry, only 16-bit signed little-endian PCM files supported");
|
Chris@31
|
136 return;
|
Chris@31
|
137 }
|
Chris@31
|
138
|
Chris@31
|
139 float rate = format.getFrameRate();
|
Chris@31
|
140 int channels = format.getChannels();
|
Chris@31
|
141 int bytesPerFrame = format.getFrameSize();
|
Chris@31
|
142 int blockSize = 1024; // frames
|
Chris@31
|
143
|
Chris@31
|
144 Plugin p = loader.loadPlugin
|
Chris@31
|
145 (pluginKey, rate, PluginLoader.AdapterFlags.ADAPT_ALL);
|
Chris@31
|
146
|
Chris@31
|
147 OutputDescriptor[] outputs = p.getOutputDescriptors();
|
Chris@31
|
148 int outputNumber = -1;
|
Chris@31
|
149 for (int i = 0; i < outputs.length; ++i) {
|
Chris@31
|
150 if (outputs[i].identifier.equals(outputKey)) outputNumber = i;
|
Chris@31
|
151 }
|
Chris@31
|
152 if (outputNumber < 0) {
|
Chris@31
|
153 System.err.println("Plugin lacks output id: " + outputKey);
|
Chris@31
|
154 System.err.print("Outputs are:");
|
Chris@31
|
155 for (int i = 0; i < outputs.length; ++i) {
|
Chris@31
|
156 System.err.print(" " + outputs[i].identifier);
|
Chris@31
|
157 }
|
Chris@31
|
158 System.err.println("");
|
Chris@31
|
159 return;
|
Chris@31
|
160 }
|
Chris@31
|
161
|
Chris@31
|
162 boolean b = p.initialise(channels, blockSize, blockSize);
|
Chris@31
|
163 if (!b) {
|
Chris@31
|
164 System.err.println("Plugin initialise failed");
|
Chris@31
|
165 return;
|
Chris@31
|
166 }
|
Chris@31
|
167
|
Chris@31
|
168 float[][] buffers = new float[channels][blockSize];
|
Chris@31
|
169
|
Chris@31
|
170 boolean done = false;
|
Chris@31
|
171 boolean incomplete = false;
|
Chris@31
|
172 int block = 0;
|
Chris@31
|
173
|
Chris@31
|
174 while (!done) {
|
Chris@31
|
175
|
Chris@31
|
176 for (int c = 0; c < channels; ++c) {
|
Chris@31
|
177 for (int i = 0; i < blockSize; ++i) {
|
Chris@31
|
178 buffers[c][i] = 0.0f;
|
Chris@31
|
179 }
|
Chris@31
|
180 }
|
Chris@31
|
181
|
Chris@31
|
182 int read = readBlock(format, stream, buffers);
|
Chris@31
|
183
|
Chris@31
|
184 if (read < 0) {
|
Chris@31
|
185 done = true;
|
Chris@31
|
186 } else {
|
Chris@31
|
187
|
Chris@31
|
188 if (incomplete) {
|
Chris@31
|
189 // An incomplete block is only OK if it's the
|
Chris@31
|
190 // last one -- so if the previous block was
|
Chris@31
|
191 // incomplete, we have trouble
|
Chris@31
|
192 System.err.println("Audio file read incomplete! Short buffer detected at " + block * blockSize);
|
Chris@31
|
193 return;
|
Chris@31
|
194 }
|
Chris@31
|
195
|
Chris@31
|
196 incomplete = (read < buffers[0].length);
|
Chris@31
|
197
|
Chris@31
|
198 RealTime timestamp = RealTime.frame2RealTime
|
Chris@31
|
199 (block * blockSize, (int)(rate + 0.5));
|
Chris@31
|
200
|
Chris@43
|
201 Map<Integer, List<Feature>>
|
Chris@31
|
202 features = p.process(buffers, timestamp);
|
Chris@31
|
203
|
Chris@31
|
204 printFeatures(timestamp, outputNumber, features);
|
Chris@31
|
205 }
|
Chris@31
|
206
|
Chris@31
|
207 ++block;
|
Chris@31
|
208 }
|
Chris@31
|
209
|
Chris@43
|
210 Map<Integer, List<Feature>>
|
Chris@31
|
211 features = p.getRemainingFeatures();
|
Chris@31
|
212
|
Chris@31
|
213 RealTime timestamp = RealTime.frame2RealTime
|
Chris@31
|
214 (block * blockSize, (int)(rate + 0.5));
|
Chris@31
|
215 printFeatures(timestamp, outputNumber, features);
|
Chris@31
|
216
|
Chris@31
|
217 p.dispose();
|
Chris@31
|
218
|
Chris@31
|
219 } catch (java.io.IOException e) {
|
Chris@31
|
220 System.err.println("Failed to read audio file: " + e.getMessage());
|
Chris@31
|
221
|
Chris@31
|
222 } catch (javax.sound.sampled.UnsupportedAudioFileException e) {
|
Chris@31
|
223 System.err.println("Unsupported audio file format: " + e.getMessage());
|
Chris@31
|
224
|
Chris@31
|
225 } catch (PluginLoader.LoadFailedException e) {
|
Chris@31
|
226 System.err.println("Plugin load failed (unknown plugin?): key is " +
|
Chris@31
|
227 key);
|
Chris@31
|
228 }
|
Chris@31
|
229 }
|
Chris@31
|
230 }
|
Chris@31
|
231
|