1 | package org.apollo.gui;
|
---|
2 |
|
---|
3 | import javax.sound.sampled.AudioFormat;
|
---|
4 |
|
---|
5 | import org.apollo.audio.SampledAudioManager;
|
---|
6 |
|
---|
7 | /**
|
---|
8 | * A WaveFormRenderer where the peaks and troughs are always chosen for every chunk of aggregated frames.
|
---|
9 | *
|
---|
10 | * @author Brook Novak
|
---|
11 | *
|
---|
12 | */
|
---|
13 | public class PeakTroughWaveFormRenderer implements WaveFormRenderer {
|
---|
14 |
|
---|
15 | private static final int DEFAULT_PEAK_TOLERANCE = 20;
|
---|
16 |
|
---|
17 | private int sampleSize;
|
---|
18 | private boolean isBigEndian;
|
---|
19 | private boolean isSigned;
|
---|
20 |
|
---|
21 | // Alternate
|
---|
22 | private boolean lastSampleHigh = false;
|
---|
23 |
|
---|
24 | /** When frames becomes more aggregated - the selected peaks are alternated evenly
|
---|
25 | * with a given tolerance*/
|
---|
26 | private int peakTolerance;
|
---|
27 |
|
---|
28 | /**
|
---|
29 | * Constructor.
|
---|
30 | *
|
---|
31 | * @param audioFormat
|
---|
32 | * The format of the audio bytes to be rendered.
|
---|
33 | *
|
---|
34 | * @throws NullPointerException
|
---|
35 | * If audio format is null.
|
---|
36 | *
|
---|
37 | * @throws IllegalArgumentException
|
---|
38 | * If audioformat is not supported. See SampledAudioManager.isFormatSupportedForPlayback
|
---|
39 | */
|
---|
40 | public PeakTroughWaveFormRenderer(AudioFormat audioFormat) {
|
---|
41 | if (audioFormat == null) throw new NullPointerException("audioFormat");
|
---|
42 |
|
---|
43 | if (!SampledAudioManager.getInstance().isFormatSupportedForPlayback(audioFormat))
|
---|
44 | throw new IllegalArgumentException();
|
---|
45 |
|
---|
46 | sampleSize = audioFormat.getSampleSizeInBits();
|
---|
47 | isSigned = audioFormat.getEncoding().toString().startsWith("PCM_SIGN");
|
---|
48 | isBigEndian = audioFormat.isBigEndian();
|
---|
49 |
|
---|
50 | peakTolerance = DEFAULT_PEAK_TOLERANCE * audioFormat.getFrameSize();
|
---|
51 | }
|
---|
52 |
|
---|
53 | /**
|
---|
54 | * {@inheritDoc}
|
---|
55 | */
|
---|
56 | public float[] getSampleAmplitudes(byte[] audioBytes, int startFrame, int frameLength, int aggregationSize) {
|
---|
57 | assert(audioBytes != null);
|
---|
58 | assert(startFrame >= 0);
|
---|
59 | assert((startFrame + frameLength) <= (audioBytes.length / (sampleSize / 8)));
|
---|
60 |
|
---|
61 | float[] amplitudes = new float[frameLength / aggregationSize];
|
---|
62 |
|
---|
63 | if (sampleSize == 16) {
|
---|
64 |
|
---|
65 | for (int i = 0; i < amplitudes.length; i++) {
|
---|
66 |
|
---|
67 | int max = 0, absmax = -1, sample, abssample; // could use short, but int avoid casting everywhere
|
---|
68 |
|
---|
69 | int startFrameIndex = (startFrame + (i * aggregationSize)) << 1;
|
---|
70 | int endFrameIndex = startFrameIndex + (aggregationSize << 1);
|
---|
71 |
|
---|
72 | for (int k = startFrameIndex; k < endFrameIndex; k+=2) {
|
---|
73 |
|
---|
74 | int lsb, msb;
|
---|
75 |
|
---|
76 | if (isBigEndian) {
|
---|
77 |
|
---|
78 | // First byte is MSB (high order)
|
---|
79 | msb = (int)audioBytes[k];
|
---|
80 |
|
---|
81 | // Second byte is LSB (low order)
|
---|
82 | lsb = (int)audioBytes[k + 1];
|
---|
83 |
|
---|
84 | } else {
|
---|
85 | // First byte is LSB (low order)
|
---|
86 | lsb = (int)audioBytes[k];
|
---|
87 |
|
---|
88 | // Second byte is MSB (high order)
|
---|
89 | msb = (int)audioBytes[k + 1];
|
---|
90 | }
|
---|
91 |
|
---|
92 | sample = (msb << 0x8) | (0xFF & lsb);
|
---|
93 |
|
---|
94 | abssample = Math.abs(sample);
|
---|
95 |
|
---|
96 | if (lastSampleHigh && sample < 0) {
|
---|
97 | abssample += peakTolerance;
|
---|
98 | } else if (!lastSampleHigh && sample > 0) {
|
---|
99 | abssample += peakTolerance;
|
---|
100 | }
|
---|
101 |
|
---|
102 | if (abssample > absmax) {
|
---|
103 | max = sample;
|
---|
104 | absmax = abssample;
|
---|
105 | }
|
---|
106 |
|
---|
107 | }
|
---|
108 |
|
---|
109 | lastSampleHigh = max > 0;
|
---|
110 |
|
---|
111 | amplitudes[i] = ((float)max) / 32768.0f;
|
---|
112 |
|
---|
113 | }
|
---|
114 |
|
---|
115 | } else if (sampleSize == 8) {
|
---|
116 |
|
---|
117 | if (isSigned) {
|
---|
118 |
|
---|
119 | // Find the peak within the block of aggregated frames
|
---|
120 | for (int i = 0; i < amplitudes.length; i++) {
|
---|
121 |
|
---|
122 | byte max = 0, absmax = -1, sample, abssample;
|
---|
123 |
|
---|
124 | int startFrameIndex = startFrame + (i * aggregationSize);
|
---|
125 | int endFrameIndex = startFrameIndex + aggregationSize;
|
---|
126 |
|
---|
127 | for (int k = startFrameIndex; k < endFrameIndex; k++) {
|
---|
128 |
|
---|
129 | sample = audioBytes[k];
|
---|
130 | abssample = (sample < 0) ? (byte)(sample * -1) : sample;
|
---|
131 |
|
---|
132 | if (abssample > absmax) {
|
---|
133 | max = sample;
|
---|
134 | absmax = abssample;
|
---|
135 | }
|
---|
136 | }
|
---|
137 |
|
---|
138 | amplitudes[i] = ((float)max) / 128.0f;
|
---|
139 |
|
---|
140 | }
|
---|
141 |
|
---|
142 | } else { // unsigned
|
---|
143 |
|
---|
144 | // Find the peak within the block of aggregated frames
|
---|
145 | for (int i = 0; i < amplitudes.length; i++) {
|
---|
146 |
|
---|
147 | int max = 0, absmax = -1, sample, abssample; // could use short, but int avoid casting everywhere
|
---|
148 |
|
---|
149 | int startFrameIndex = startFrame + (i * aggregationSize);
|
---|
150 | int endFrameIndex = startFrameIndex + aggregationSize;
|
---|
151 |
|
---|
152 | for (int k = startFrameIndex; k < endFrameIndex; k++) {
|
---|
153 |
|
---|
154 | sample = (audioBytes[k] & 0xFF) - 128;
|
---|
155 | abssample = Math.abs(sample);
|
---|
156 |
|
---|
157 | if (abssample > absmax) {
|
---|
158 | max = sample;
|
---|
159 | absmax = abssample;
|
---|
160 | }
|
---|
161 | }
|
---|
162 |
|
---|
163 | amplitudes[i] = ((float)max) / 128.0f;
|
---|
164 |
|
---|
165 | }
|
---|
166 |
|
---|
167 | }
|
---|
168 |
|
---|
169 | }
|
---|
170 |
|
---|
171 | return amplitudes;
|
---|
172 | }
|
---|
173 |
|
---|
174 | }
|
---|