| 1 | package org.farng.mp3.id3; |
| 2 | |
| 3 | import org.farng.mp3.InvalidTagException; |
| 4 | |
| 5 | import java.io.IOException; |
| 6 | import java.io.RandomAccessFile; |
| 7 | |
| 8 | /** |
| 9 | * <h3>4.12.Relative volume adjustment</h3> |
| 10 | * <p/> |
| 11 | * <p class=t> This is a more subjective function than the previous ones. It allows the user to say how much he wants to |
| 12 | * increase/decrease the volume on each channel while the file is played. The purpose is to be able to align all files |
| 13 | * to a reference volume, so that you don't have to change the volume constantly. This frame may also be used to balance |
| 14 | * adjust the audio. If the volume peak levels are known then this could be described with the 'Peak volume right' and |
| 15 | * 'Peak volume left' field. If Peakvolume is not known these fields could be left zeroed or, if no other data follows, |
| 16 | * be completely omitted. There may only be one "RVAD" frame in each tag. |
| 17 | * <p/> |
| 18 | * </p> |
| 19 | * <p/> |
| 20 | * <p><center> <table border=0> <tr><td colspan=2><Header for 'Relative volume adjustment', ID: "RVAD"></td></tr> |
| 21 | * <tr><td>Increment/decrement</td><td>%00xxxxxx</td></tr> <tr><td>Bits used for volume descr.</td><td>$xx</td></tr> |
| 22 | * <tr><td>Relative volume change, right</td><td>$xx xx (xx ...)</td></tr> |
| 23 | * <p/> |
| 24 | * <tr><td>Relative volume change, left</td><td>$xx xx (xx ...)</td></tr> <tr><td>Peak volume right</td><td>$xx xx (xx |
| 25 | * ...)</td></tr> <tr><td>Peak volume left</td><td>$xx xx (xx ...)</td></tr> </table> </center> |
| 26 | * <p/> |
| 27 | * <p class=t> |
| 28 | * <p/> |
| 29 | * In the increment/decrement field bit 0 is used to indicate the right channel and bit 1 is used to indicate the left |
| 30 | * channel. 1 is increment and 0 is decrement. </p> |
| 31 | * <p/> |
| 32 | * <p class=t> The 'bits used for volume description' field is normally $10 (16 bits) for <a href="#MPEG">MPEG</a> 2 |
| 33 | * layer I, II and III and MPEG 2.5. This value may not be $00. The volume is always represented with whole bytes, |
| 34 | * padded in the beginning (highest bits) when 'bits used for volume description' is not a multiple of eight. </p> |
| 35 | * <p/> |
| 36 | * <p class=t> This datablock is then optionally followed by a volume definition for the left and right back channels. |
| 37 | * If this information is appended to the frame the first two channels will be treated as front channels. In the |
| 38 | * increment/decrement field bit 2 is used to indicate the right back channel and bit 3 for the left back channel. </p> |
| 39 | * <p/> |
| 40 | * <p><center> <table border=0> <tr><td nowrap>Relative volume change, right back</td><td>$xx xx (xx ...)</td></tr> |
| 41 | * <tr><td>Relative volume change, left back</td><td>$xx xx (xx ...)</td></tr> <tr><td>Peak volume right |
| 42 | * back</td><td>$xx xx (xx ...)</td></tr> <tr><td>Peak volume left back</td><td>$xx xx (xx ...)</td></tr> |
| 43 | * <p/> |
| 44 | * </table> </center> |
| 45 | * <p/> |
| 46 | * <p class=t> If the center channel adjustment is present the following is appended to the existing frame, after the |
| 47 | * left and right back channels. The center channel is represented by bit 4 in the increase/decrease field. </p> |
| 48 | * <p/> |
| 49 | * <p><center> <table border=0> <tr><td nowrap>Relative volume change, center</td><td>$xx xx (xx ...)</td></tr> |
| 50 | * <tr><td>Peak volume center</td><td>$xx xx (xx ...)</td></tr> |
| 51 | * <p/> |
| 52 | * </table> </center> |
| 53 | * <p/> |
| 54 | * <p class=t> If the bass channel adjustment is present the following is appended to the existing frame, after the |
| 55 | * center channel. The bass channel is represented by bit 5 in the increase/decrease field. </p> |
| 56 | * <p/> |
| 57 | * <p><center> <table border=0> <tr><td nowrap>Relative volume change, bass</td><td>$xx xx (xx ...)</td></tr> |
| 58 | * <tr><td>Peak volume bass</td><td>$xx xx (xx ...)</td></tr> |
| 59 | * <p/> |
| 60 | * </table> </center> |
| 61 | * |
| 62 | * @author Eric Farng` |
| 63 | * @version $Revision: 1.5 $ |
| 64 | */ |
| 65 | public class FrameBodyRVAD extends AbstractID3v2FrameBody { |
| 66 | |
| 67 | byte bytesUsed = 16; |
| 68 | byte increment = 0; |
| 69 | long peakBass = 0; |
| 70 | long peakCenter = 0; |
| 71 | long peakLeft = 0; |
| 72 | long peakLeftBack = 0; |
| 73 | long peakRight = 0; |
| 74 | long peakRightBack = 0; |
| 75 | long relativeBass = 0; |
| 76 | long relativeCenter = 0; |
| 77 | long relativeLeft = 0; |
| 78 | long relativeLeftBack = 0; |
| 79 | long relativeRight = 0; |
| 80 | long relativeRightBack = 0; |
| 81 | |
| 82 | /** |
| 83 | * Creates a new FrameBodyRVAD object. |
| 84 | */ |
| 85 | public FrameBodyRVAD() { |
| 86 | super(); |
| 87 | } |
| 88 | |
| 89 | /** |
| 90 | * Creates a new FrameBodyRVAD object. |
| 91 | */ |
| 92 | public FrameBodyRVAD(final FrameBodyRVAD copyObject) { |
| 93 | super(copyObject); |
| 94 | this.bytesUsed = copyObject.bytesUsed; |
| 95 | this.increment = copyObject.increment; |
| 96 | this.peakBass = copyObject.peakBass; |
| 97 | this.peakCenter = copyObject.peakCenter; |
| 98 | this.peakLeft = copyObject.peakLeft; |
| 99 | this.peakLeftBack = copyObject.peakLeftBack; |
| 100 | this.peakRight = copyObject.peakRight; |
| 101 | this.peakRightBack = copyObject.peakRightBack; |
| 102 | this.relativeBass = copyObject.relativeBass; |
| 103 | this.relativeCenter = copyObject.relativeCenter; |
| 104 | this.relativeLeft = copyObject.relativeLeft; |
| 105 | this.relativeLeftBack = copyObject.relativeLeftBack; |
| 106 | this.relativeRight = copyObject.relativeRight; |
| 107 | this.relativeRightBack = copyObject.relativeRightBack; |
| 108 | } |
| 109 | |
| 110 | /** |
| 111 | * Creates a new FrameBodyRVAD object. |
| 112 | */ |
| 113 | public FrameBodyRVAD(final byte increment, |
| 114 | final byte bitsUsed, |
| 115 | final long relativeRight, |
| 116 | final long relativeLeft, |
| 117 | final long peakRight, |
| 118 | final long peakLeft, |
| 119 | final long relativeRightBack, |
| 120 | final long relativeLeftBack, |
| 121 | final long peakRightBack, |
| 122 | final long peakLeftBack, |
| 123 | final long relativeCenter, |
| 124 | final long peakCenter, |
| 125 | final long relativeBass, |
| 126 | final long peakBass) { |
| 127 | this.increment = increment; |
| 128 | this.bytesUsed = (byte) (((bitsUsed - 1) / 8) + 1); // convert to bytes. |
| 129 | this.relativeRight = relativeRight; |
| 130 | this.relativeLeft = relativeLeft; |
| 131 | this.peakRight = peakRight; |
| 132 | this.peakLeft = peakLeft; |
| 133 | this.relativeRightBack = relativeRightBack; |
| 134 | this.relativeLeftBack = relativeLeftBack; |
| 135 | this.peakRightBack = peakRightBack; |
| 136 | this.peakLeftBack = peakLeftBack; |
| 137 | this.relativeCenter = relativeCenter; |
| 138 | this.peakCenter = peakCenter; |
| 139 | this.relativeBass = relativeBass; |
| 140 | this.peakBass = peakBass; |
| 141 | } |
| 142 | |
| 143 | /** |
| 144 | * Creates a new FrameBodyRVAD object. |
| 145 | */ |
| 146 | public FrameBodyRVAD(final RandomAccessFile file) throws IOException, InvalidTagException { |
| 147 | this.read(file); |
| 148 | } |
| 149 | |
| 150 | public String getIdentifier() { |
| 151 | return "RVAD"; |
| 152 | } |
| 153 | |
| 154 | public int getSize() { |
| 155 | int size = 2 + (this.bytesUsed * 4); |
| 156 | if ((this.relativeRightBack != 0) || |
| 157 | (this.relativeLeftBack != 0) || |
| 158 | (this.peakRightBack != 0) || |
| 159 | (this.peakLeftBack != 0)) { |
| 160 | size += (this.bytesUsed * 4); |
| 161 | } |
| 162 | if ((this.relativeCenter != 0) || (this.peakCenter != 0)) { |
| 163 | size += (this.bytesUsed * 2); |
| 164 | } |
| 165 | if ((this.relativeBass != 0) || (this.peakBass != 0)) { |
| 166 | size += (this.bytesUsed * 2); |
| 167 | } |
| 168 | return size; |
| 169 | } |
| 170 | |
| 171 | /** |
| 172 | * This method is not yet supported. |
| 173 | * |
| 174 | * @throws java.lang.UnsupportedOperationException |
| 175 | * This method is not yet supported |
| 176 | */ |
| 177 | public void equals() { |
| 178 | //todo Implement this java.lang.Object method |
| 179 | throw new java.lang.UnsupportedOperationException("Method equals() not yet implemented."); |
| 180 | } |
| 181 | |
| 182 | protected void setupObjectList() { |
| 183 | throw new UnsupportedOperationException(); |
| 184 | } |
| 185 | |
| 186 | public void read(final RandomAccessFile file) throws IOException, InvalidTagException { |
| 187 | final int size; |
| 188 | int offset = 0; |
| 189 | final byte[] buffer; |
| 190 | size = readHeader(file); |
| 191 | buffer = new byte[size]; |
| 192 | file.read(buffer); |
| 193 | this.increment = buffer[offset++]; |
| 194 | this.bytesUsed = (byte) (((buffer[offset++] - 1) / 8 * 8) + 1); |
| 195 | for (int i = 0; i < this.bytesUsed; i++) { |
| 196 | this.relativeRight = (this.relativeRight << 8) + buffer[i + offset]; |
| 197 | } |
| 198 | offset += this.bytesUsed; |
| 199 | for (int i = 0; i < this.bytesUsed; i++) { |
| 200 | this.relativeLeft = (this.relativeLeft << 8) + buffer[i + offset]; |
| 201 | } |
| 202 | offset += this.bytesUsed; |
| 203 | for (int i = 0; i < this.bytesUsed; i++) { |
| 204 | this.peakRight = (this.peakRight << 8) + buffer[i + offset]; |
| 205 | } |
| 206 | offset += this.bytesUsed; |
| 207 | for (int i = 0; i < this.bytesUsed; i++) { |
| 208 | this.peakLeft = (this.peakLeft << 8) + buffer[i + offset]; |
| 209 | } |
| 210 | offset += this.bytesUsed; |
| 211 | if (size > (2 + (this.bytesUsed * 4))) { |
| 212 | for (int i = 0; i < this.bytesUsed; i++) { |
| 213 | this.relativeRightBack = (this.relativeRightBack << 8) + buffer[i + offset]; |
| 214 | } |
| 215 | offset += this.bytesUsed; |
| 216 | for (int i = 0; i < this.bytesUsed; i++) { |
| 217 | this.relativeLeftBack = (this.relativeLeftBack << 8) + buffer[i + offset]; |
| 218 | } |
| 219 | offset += this.bytesUsed; |
| 220 | for (int i = 0; i < this.bytesUsed; i++) { |
| 221 | this.peakRightBack = (this.peakRightBack << 8) + buffer[i + offset]; |
| 222 | } |
| 223 | offset += this.bytesUsed; |
| 224 | for (int i = 0; i < this.bytesUsed; i++) { |
| 225 | this.peakLeftBack = (this.peakLeftBack << 8) + buffer[i + offset]; |
| 226 | } |
| 227 | offset += this.bytesUsed; |
| 228 | } |
| 229 | if (size > (2 + (this.bytesUsed * 8))) { |
| 230 | for (int i = 0; i < this.bytesUsed; i++) { |
| 231 | this.relativeCenter = (this.relativeCenter << 8) + buffer[i + offset]; |
| 232 | } |
| 233 | offset += this.bytesUsed; |
| 234 | for (int i = 0; i < this.bytesUsed; i++) { |
| 235 | this.peakCenter = (this.peakCenter << 8) + buffer[i + offset]; |
| 236 | } |
| 237 | offset += this.bytesUsed; |
| 238 | } |
| 239 | if (size > (2 + (this.bytesUsed * 10))) { |
| 240 | for (int i = 0; i < this.bytesUsed; i++) { |
| 241 | this.relativeBass = (this.relativeBass << 8) + buffer[i + offset]; |
| 242 | } |
| 243 | offset += this.bytesUsed; |
| 244 | for (int i = 0; i < this.bytesUsed; i++) { |
| 245 | this.peakBass = (this.peakBass << 8) + buffer[i + offset]; |
| 246 | } |
| 247 | offset += this.bytesUsed; |
| 248 | } |
| 249 | } |
| 250 | |
| 251 | public String toString() { |
| 252 | return this |
| 253 | .increment + |
| 254 | " " + |
| 255 | (this.bytesUsed * 8) + |
| 256 | " " + |
| 257 | // convert back to bits |
| 258 | this |
| 259 | .relativeRight + |
| 260 | " " + |
| 261 | this |
| 262 | .relativeLeft + |
| 263 | " " + |
| 264 | this |
| 265 | .peakRight + |
| 266 | " " + |
| 267 | this |
| 268 | .peakLeft + |
| 269 | " " + |
| 270 | this |
| 271 | .relativeRightBack + |
| 272 | " " + |
| 273 | this |
| 274 | .relativeLeftBack + |
| 275 | " " + |
| 276 | this |
| 277 | .peakRightBack + |
| 278 | " " + |
| 279 | this |
| 280 | .peakLeftBack + |
| 281 | " " + |
| 282 | this |
| 283 | .relativeCenter + |
| 284 | " " + |
| 285 | this |
| 286 | .peakCenter + |
| 287 | " " + |
| 288 | this |
| 289 | .relativeBass + |
| 290 | " " + |
| 291 | this |
| 292 | .peakBass; |
| 293 | } |
| 294 | |
| 295 | public void write(final RandomAccessFile file) throws IOException { |
| 296 | final byte[] buffer; |
| 297 | int offset = 0; |
| 298 | writeHeader(file, this.getSize()); |
| 299 | buffer = new byte[this.getSize()]; |
| 300 | buffer[offset++] = this.increment; |
| 301 | buffer[offset++] = this.bytesUsed; |
| 302 | for (int i = 0; i < this.bytesUsed; i++) { |
| 303 | buffer[i + offset] = (byte) (this.relativeRight >> ((this.bytesUsed - i - 1) * 8)); |
| 304 | } |
| 305 | offset += this.bytesUsed; |
| 306 | for (int i = 0; i < this.bytesUsed; i++) { |
| 307 | buffer[i + offset] = (byte) (this.relativeLeft >> ((this.bytesUsed - i - 1) * 8)); |
| 308 | } |
| 309 | offset += this.bytesUsed; |
| 310 | for (int i = 0; i < this.bytesUsed; i++) { |
| 311 | buffer[i + offset] = (byte) (this.peakRight >> ((this.bytesUsed - i - 1) * 8)); |
| 312 | } |
| 313 | offset += this.bytesUsed; |
| 314 | for (int i = 0; i < this.bytesUsed; i++) { |
| 315 | buffer[i + offset] = (byte) (this.peakLeft >> ((this.bytesUsed - i - 1) * 8)); |
| 316 | } |
| 317 | offset += this.bytesUsed; |
| 318 | if ((this.relativeRightBack != 0) || |
| 319 | (this.relativeLeftBack != 0) || |
| 320 | (this.peakRightBack != 0) || |
| 321 | (this.peakLeftBack != 0)) { |
| 322 | for (int i = 0; i < this.bytesUsed; i++) { |
| 323 | buffer[i + offset] = (byte) (this.relativeRightBack >> ((this.bytesUsed - i - 1) * 8)); |
| 324 | } |
| 325 | offset += this.bytesUsed; |
| 326 | for (int i = 0; i < this.bytesUsed; i++) { |
| 327 | buffer[i + offset] = (byte) (this.relativeLeftBack >> ((this.bytesUsed - i - 1) * 8)); |
| 328 | } |
| 329 | offset += this.bytesUsed; |
| 330 | for (int i = 0; i < this.bytesUsed; i++) { |
| 331 | buffer[i + offset] = (byte) (this.peakRightBack >> ((this.bytesUsed - i - 1) * 8)); |
| 332 | } |
| 333 | offset += this.bytesUsed; |
| 334 | for (int i = 0; i < this.bytesUsed; i++) { |
| 335 | buffer[i + offset] = (byte) (this.peakLeftBack >> ((this.bytesUsed - i - 1) * 8)); |
| 336 | } |
| 337 | offset += this.bytesUsed; |
| 338 | } |
| 339 | if ((this.relativeCenter != 0) || (this.peakCenter != 0)) { |
| 340 | for (int i = 0; i < this.bytesUsed; i++) { |
| 341 | buffer[i + offset] = (byte) (this.relativeCenter >> ((this.bytesUsed - i - 1) * 8)); |
| 342 | } |
| 343 | offset += this.bytesUsed; |
| 344 | for (int i = 0; i < this.bytesUsed; i++) { |
| 345 | buffer[i + offset] = (byte) (this.peakCenter >> ((this.bytesUsed - i - 1) * 8)); |
| 346 | } |
| 347 | offset += this.bytesUsed; |
| 348 | } |
| 349 | if ((this.relativeBass != 0) || (this.peakBass != 0)) { |
| 350 | for (int i = 0; i < this.bytesUsed; i++) { |
| 351 | buffer[i + offset] = (byte) (this.relativeBass >> ((this.bytesUsed - i - 1) * 8)); |
| 352 | } |
| 353 | offset += this.bytesUsed; |
| 354 | for (int i = 0; i < this.bytesUsed; i++) { |
| 355 | buffer[i + offset] = (byte) (this.peakBass >> ((this.bytesUsed - i - 1) * 8)); |
| 356 | } |
| 357 | offset += this.bytesUsed; |
| 358 | } |
| 359 | file.write(buffer); |
| 360 | } |
| 361 | } |