1 package com.melloware.jukes.gui.view.component;
2
3 import java.awt.Color;
4 import java.awt.Cursor;
5 import java.awt.Graphics;
6 import java.awt.Image;
7 import java.awt.event.MouseAdapter;
8 import java.awt.event.MouseEvent;
9 import java.io.BufferedReader;
10 import java.io.ByteArrayInputStream;
11 import java.io.IOException;
12 import java.io.InputStreamReader;
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.Collections;
16 import java.util.List;
17 import java.util.StringTokenizer;
18
19 import javax.sound.sampled.SourceDataLine;
20 import javax.swing.JPanel;
21
22 import kj.dsp.KJDigitalSignalProcessingAudioDataConsumer;
23 import kj.dsp.KJDigitalSignalProcessor;
24 import kj.dsp.KJFFT;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28
29
30
31
32
33
34
35
36 @SuppressWarnings("PMD")
37 public final class SpectrumTimeAnalyzer extends JPanel implements KJDigitalSignalProcessor {
38
39 private static final Log LOG = LogFactory.getLog(SpectrumTimeAnalyzer.class);
40
41 public static final String SCOPE = "Scope";
42 public static final String OFF = "Off";
43 public static final String ANALYZER = "Analyzer";
44 public static final int DISPLAY_MODE_SCOPE = 0;
45 public static final int DISPLAY_MODE_SPECTRUM_ANALYSER = 1;
46 public static final int DISPLAY_MODE_OFF = 2;
47 public static final int DEFAULT_WIDTH = 256;
48 public static final int DEFAULT_HEIGHT = 128;
49 public static final int DEFAULT_FPS = 50;
50 public static final int DEFAULT_SPECTRUM_ANALYSER_FFT_SAMPLE_SIZE = 512;
51 public static final int DEFAULT_SPECTRUM_ANALYSER_BAND_COUNT = 19;
52 public static final float DEFAULT_SPECTRUM_ANALYSER_DECAY = 0.05f;
53 public static final int DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY = 20;
54 public static final float DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY_FPS_RATIO = 0.4f;
55 public static final float DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY_FPS_RATIO_RANGE = 0.1f;
56 public static final float MIN_SPECTRUM_ANALYSER_DECAY = 0.02f;
57 public static final float MAX_SPECTRUM_ANALYSER_DECAY = 0.08f;
58 public static final Color DEFAULT_BACKGROUND_COLOR = new Color(0, 0, 128);
59 public static final Color DEFAULT_SCOPE_COLOR = new Color(255, 192, 0);
60 public static final float DEFAULT_VU_METER_DECAY = 0.02f;
61 private transient Image bi;
62 private int displayMode = DISPLAY_MODE_SCOPE;
63 private Color scopeColor = DEFAULT_SCOPE_COLOR;
64 private Color[] spectrumAnalyserColors = getDefaultSpectrumAnalyserColors();
65 private static final KJDigitalSignalProcessingAudioDataConsumer dsp = new KJDigitalSignalProcessingAudioDataConsumer(
66 2048, DEFAULT_FPS);
67 private boolean dspStarted = false;
68 private Color peakColor = null;
69 private int[] peaks = new int[DEFAULT_SPECTRUM_ANALYSER_BAND_COUNT];
70 private int[] peaksDelay = new int[DEFAULT_SPECTRUM_ANALYSER_BAND_COUNT];
71 private int peakDelay = DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY;
72 private boolean peaksEnabled = true;
73 private int barOffset = 1;
74 private int width;
75 private int height;
76 private int height_2;
77
78 private static final KJFFT fft = new KJFFT(DEFAULT_SPECTRUM_ANALYSER_FFT_SAMPLE_SIZE);
79 private float[] old_FFT;
80 private int saFFTSampleSize;
81 private int saBands;
82 private float saColorScale;
83 private float saMultiplier;
84 private float saDecay = DEFAULT_SPECTRUM_ANALYSER_DECAY;
85 private transient SourceDataLine m_line = null;
86
87 public SpectrumTimeAnalyzer() {
88 setOpaque(false);
89 initialize();
90 }
91
92 public boolean isPeaksEnabled() {
93 return peaksEnabled;
94 }
95
96 public void setPeaksEnabled(boolean peaksEnabled) {
97 this.peaksEnabled = peaksEnabled;
98 }
99
100
101
102
103
104 public synchronized void startDSP(SourceDataLine line) {
105 if (displayMode == DISPLAY_MODE_OFF)
106 return;
107 if (line != null)
108 m_line = line;
109
110 if (m_line != null) {
111 if (dspStarted == true) {
112 stopDSP();
113 }
114 dsp.start(m_line);
115 dspStarted = true;
116 LOG.debug("DSP started");
117 }
118 }
119
120
121
122
123 public synchronized void stopDSP() {
124 if (dspStarted) {
125 dsp.stop();
126 dspStarted = false;
127 LOG.debug("DSP stopped");
128 }
129 }
130
131
132
133
134
135 public synchronized void setupDSP(SourceDataLine line) {
136 int channels = line.getFormat().getChannels();
137 if (channels == 1)
138 dsp.setChannelMode(KJDigitalSignalProcessingAudioDataConsumer.CHANNEL_MODE_MONO);
139 else
140 dsp.setChannelMode(KJDigitalSignalProcessingAudioDataConsumer.CHANNEL_MODE_STEREO);
141 int bits = line.getFormat().getSampleSizeInBits();
142 if (bits == 8)
143 dsp.setSampleType(KJDigitalSignalProcessingAudioDataConsumer.SAMPLE_TYPE_EIGHT_BIT);
144 else
145 dsp.setSampleType(KJDigitalSignalProcessingAudioDataConsumer.SAMPLE_TYPE_SIXTEEN_BIT);
146 }
147
148
149
150
151
152 public void writeDSP(byte[] pcmdata) {
153 if (dspStarted == true) {
154 dsp.writeAudioData(pcmdata);
155 }
156 }
157
158
159
160
161
162 public KJDigitalSignalProcessingAudioDataConsumer getDSP() {
163 return dsp;
164 }
165
166
167
168
169
170 @SuppressWarnings("unchecked")
171 public void setVisColor(String viscolor) {
172 ArrayList visColors = new ArrayList();
173 viscolor = viscolor.toLowerCase();
174 ByteArrayInputStream in = new ByteArrayInputStream(viscolor.getBytes());
175 BufferedReader bin = new BufferedReader(new InputStreamReader(in));
176 try {
177 String line = null;
178 while ((line = bin.readLine()) != null) {
179 visColors.add(getColor(line));
180 }
181 Color[] colors = new Color[visColors.size()];
182 visColors.toArray(colors);
183 Color[] specColors = new Color[15];
184 System.arraycopy(colors, 2, specColors, 0, 15);
185 List specList = Arrays.asList(specColors);
186 Collections.reverse(specList);
187 specColors = (Color[]) specList.toArray(specColors);
188 setSpectrumAnalyserColors(specColors);
189 setBackground((Color) visColors.get(0));
190 if (visColors.size() > 23)
191 setPeakColor((Color) visColors.get(23));
192 if (visColors.size() > 18)
193 setScopeColor((Color) visColors.get(18));
194 } catch (IOException ex) {
195 LOG.warn("Cannot parse viscolors", ex);
196 } finally {
197 try {
198 if (bin != null)
199 bin.close();
200 } catch (IOException e) {
201 }
202 }
203 }
204
205
206
207
208
209 public synchronized void setPeakColor(Color c) {
210 peakColor = c;
211 }
212
213
214
215
216
217 public void setPeakDelay(int framestowait) {
218 int min = (int) Math
219 .round((DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY_FPS_RATIO - DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY_FPS_RATIO_RANGE)
220 * DEFAULT_FPS);
221 int max = (int) Math
222 .round((DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY_FPS_RATIO + DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY_FPS_RATIO_RANGE)
223 * DEFAULT_FPS);
224 if ((framestowait >= min) && (framestowait <= max)) {
225 peakDelay = framestowait;
226 } else {
227 peakDelay = (int) Math.round(DEFAULT_SPECTRUM_ANALYSER_PEAK_DELAY_FPS_RATIO * DEFAULT_FPS);
228 }
229 }
230
231
232
233
234
235 public int getPeakDelay() {
236 return peakDelay;
237 }
238
239
240
241
242
243
244 public Color getColor(String linecolor) {
245 Color color = Color.BLACK;
246 StringTokenizer st = new StringTokenizer(linecolor, ",");
247 int red = 0, green = 0, blue = 0;
248 try {
249 if (st.hasMoreTokens())
250 red = Integer.parseInt(st.nextToken().trim());
251 if (st.hasMoreTokens())
252 green = Integer.parseInt(st.nextToken().trim());
253 if (st.hasMoreTokens()) {
254 String blueStr = st.nextToken().trim();
255 if (blueStr.length() > 3)
256 blueStr = (blueStr.substring(0, 3)).trim();
257 blue = Integer.parseInt(blueStr);
258 }
259 color = new Color(red, green, blue);
260 } catch (NumberFormatException e) {
261 LOG.debug("Cannot parse viscolor : " + e.getMessage());
262 }
263 return color;
264 }
265
266 private synchronized void computeColorScale() {
267 saColorScale = ((float) spectrumAnalyserColors.length / height) * barOffset * 1.0f;
268 }
269
270 private synchronized void computeSAMultiplier() {
271 saMultiplier = (saFFTSampleSize / 2) / saBands;
272 }
273
274 private synchronized void drawScope(Graphics pGrp, float[] pSample) {
275 pGrp.setColor(scopeColor);
276 int wLas = (int) (pSample[0] * (float) height_2) + height_2;
277 int wSt = 2;
278 for (int a = wSt, c = 0; c < width; a += wSt, c++) {
279 int wAs = (int) (pSample[a] * (float) height_2) + height_2;
280 pGrp.drawLine(c, wLas, c + 1, wAs);
281 wLas = wAs;
282 }
283 }
284
285 private synchronized void drawSpectrumAnalyser(Graphics pGrp, float[] pSample, float pFrrh) {
286 float c = 0;
287 float[] wFFT = fft.calculate(pSample);
288 float wSadfrr = (saDecay * pFrrh);
289 float wBw = ((float) width / (float) saBands);
290 for (int a = 0, bd = 0; bd < saBands; a += saMultiplier, bd++) {
291 float wFs = 0;
292
293 for (int b = 0; b < saMultiplier; b++) {
294 wFs += wFFT[a + b];
295 }
296
297 wFs = (wFs * (float) Math.log(bd + 2));
298 if (wFs > 1.0f) {
299 wFs = 1.0f;
300 }
301
302 if (wFs >= (old_FFT[a] - wSadfrr)) {
303 old_FFT[a] = wFs;
304 } else {
305 old_FFT[a] -= wSadfrr;
306 if (old_FFT[a] < 0) {
307 old_FFT[a] = 0;
308 }
309 wFs = old_FFT[a];
310 }
311 drawSpectrumAnalyserBar(pGrp, (int) c, height, (int) wBw - 1, (int) (wFs * height), bd);
312 c += wBw;
313 }
314 }
315
316 private synchronized void drawSpectrumAnalyserBar(Graphics pGraphics, int pX, int pY, int pWidth, int pHeight,
317 int band) {
318 float c = 0;
319 for (int a = pY; a >= pY - pHeight; a -= barOffset) {
320 c += saColorScale;
321 if (c < spectrumAnalyserColors.length) {
322 pGraphics.setColor(spectrumAnalyserColors[(int) c]);
323 }
324 pGraphics.fillRect(pX, a, pWidth, 1);
325 }
326 if ((peakColor != null) && (peaksEnabled == true)) {
327 pGraphics.setColor(peakColor);
328 if (pHeight > peaks[band]) {
329 peaks[band] = pHeight;
330 peaksDelay[band] = peakDelay;
331 } else {
332 peaksDelay[band]--;
333 if (peaksDelay[band] < 0)
334 peaks[band]--;
335 if (peaks[band] < 0)
336 peaks[band] = 0;
337 }
338 pGraphics.fillRect(pX, pY - peaks[band], pWidth, 1);
339 }
340 }
341
342 private synchronized Image getDoubleBuffer() {
343 if (bi == null || (bi.getWidth(null) != getSize().width || bi.getHeight(null) != getSize().height)) {
344 width = getSize().width;
345 height = getSize().height;
346 height_2 = height >> 1;
347 computeColorScale();
348 bi = getGraphicsConfiguration().createCompatibleVolatileImage(width, height);
349 }
350 return bi;
351 }
352
353 public static Color[] getDefaultSpectrumAnalyserColors() {
354 Color[] wColors = new Color[256];
355 for (int a = 0; a < 128; a++) {
356 wColors[a] = new Color(0, (a >> 1) + 192, 0);
357 }
358 for (int a = 0; a < 64; a++) {
359 wColors[a + 128] = new Color(a << 2, 255, 0);
360 }
361 for (int a = 0; a < 64; a++) {
362 wColors[a + 192] = new Color(255, 255 - (a << 2), 0);
363 }
364 return wColors;
365 }
366
367
368
369
370
371 public synchronized int getDisplayMode() {
372 return displayMode;
373 }
374
375
376
377
378
379 public synchronized int getSpectrumAnalyserBandCount() {
380 return saBands;
381 }
382
383
384
385
386 public synchronized float getSpectrumAnalyserDecay() {
387 return saDecay;
388 }
389
390
391
392
393 public synchronized Color getScopeColor() {
394 return scopeColor;
395 }
396
397
398
399
400 public synchronized Color[] getSpectrumAnalyserColors() {
401 return spectrumAnalyserColors;
402 }
403
404 private void initialize() {
405 setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
406 setBackground(DEFAULT_BACKGROUND_COLOR);
407 prepareDisplayToggleListener();
408 setSpectrumAnalyserBandCount(DEFAULT_SPECTRUM_ANALYSER_BAND_COUNT);
409 setSpectrumAnalyserFFTSampleSize(DEFAULT_SPECTRUM_ANALYSER_FFT_SAMPLE_SIZE);
410 dsp.add(this);
411 }
412
413 public void paintComponent(Graphics pGraphics) {
414 synchronized (this) {
415 if (displayMode == DISPLAY_MODE_OFF)
416 return;
417
418 if (dspStarted) {
419 pGraphics.drawImage(getDoubleBuffer(), 0, 0, null);
420 } else {
421 super.paintComponent(pGraphics);
422 }
423 }
424 }
425
426 private void prepareDisplayToggleListener() {
427 setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
428 addMouseListener(new MouseAdapter() {
429 public void mouseClicked(MouseEvent pEvent) {
430 if (pEvent.getButton() == MouseEvent.BUTTON1) {
431 if (displayMode + 1 > 2) {
432 displayMode = 0;
433 } else {
434 displayMode++;
435 }
436 }
437 }
438 });
439 }
440
441
442
443
444
445 public synchronized void process(float[] pLeft, float[] pRight, float pFrameRateRatioHint) {
446 if (displayMode == DISPLAY_MODE_OFF)
447 return;
448 final Graphics wGrp = getDoubleBuffer().getGraphics();
449 wGrp.setColor(getBackground());
450 wGrp.fillRect(0, 0, getSize().width, getSize().height);
451 switch (displayMode) {
452 case DISPLAY_MODE_SCOPE:
453 drawScope(wGrp, stereoMerge(pLeft, pRight));
454 break;
455 case DISPLAY_MODE_SPECTRUM_ANALYSER:
456 drawSpectrumAnalyser(wGrp, stereoMerge(pLeft, pRight), pFrameRateRatioHint);
457 break;
458 }
459 final Graphics graphics = getGraphics();
460 if (graphics != null)
461 graphics.drawImage(getDoubleBuffer(), 0, 0, null);
462 }
463
464
465
466
467
468
469 public synchronized void setDisplayMode(int pMode) {
470 displayMode = pMode;
471 }
472
473
474
475
476
477
478
479 public synchronized void setDisplayMode(String pMode) {
480 if (ANALYZER.equals(pMode)) {
481 this.setDisplayMode(DISPLAY_MODE_SPECTRUM_ANALYSER);
482 } else if (SCOPE.equals(pMode)) {
483 this.setDisplayMode(DISPLAY_MODE_SCOPE);
484 } else {
485 this.setDisplayMode(DISPLAY_MODE_OFF);
486 }
487 }
488
489
490
491
492
493 public synchronized void setScopeColor(Color pColor) {
494 scopeColor = pColor;
495 }
496
497
498
499
500
501 public synchronized void setSpectrumAnalyserBandCount(int pCount) {
502 saBands = pCount;
503 peaks = new int[saBands];
504 peaksDelay = new int[saBands];
505 computeSAMultiplier();
506 }
507
508
509
510
511
512 public synchronized void setSpectrumAnalyserDecay(float pDecay) {
513 if ((pDecay >= MIN_SPECTRUM_ANALYSER_DECAY) && (pDecay <= MAX_SPECTRUM_ANALYSER_DECAY)) {
514 saDecay = pDecay;
515 } else
516 saDecay = DEFAULT_SPECTRUM_ANALYSER_DECAY;
517 }
518
519
520
521
522
523 public synchronized void setSpectrumAnalyserColors(Color[] pColors) {
524 spectrumAnalyserColors = pColors;
525 computeColorScale();
526 }
527
528
529
530
531
532
533
534 public synchronized void setSpectrumAnalyserFFTSampleSize(int pSize) {
535 saFFTSampleSize = pSize;
536 old_FFT = new float[saFFTSampleSize];
537 computeSAMultiplier();
538 }
539
540 private float[] stereoMerge(float[] pLeft, float[] pRight) {
541 for (int a = 0; a < pLeft.length; a++) {
542 pLeft[a] = (pLeft[a] + pRight[a]) / 2.0f;
543 }
544 return pLeft;
545 }
546
547 }