1 /* 2 * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.media.sound; 27 28 import java.io.ByteArrayOutputStream; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.util.Arrays; 32 33 import javax.sound.sampled.AudioFormat; 34 import javax.sound.sampled.AudioInputStream; 35 import javax.sound.sampled.AudioSystem; 36 import javax.sound.sampled.Clip; 37 import javax.sound.sampled.DataLine; 38 import javax.sound.sampled.LineEvent; 39 import javax.sound.sampled.LineUnavailableException; 40 41 /** 42 * Clip implementation for the SoftMixingMixer. 43 * 44 * @author Karl Helgason 45 */ 46 public final class SoftMixingClip extends SoftMixingDataLine implements Clip { 47 48 private AudioFormat format; 49 50 private int framesize; 51 52 private byte[] data; 53 54 private final InputStream datastream = new InputStream() { 55 56 @Override 57 public int read() throws IOException { 58 byte[] b = new byte[1]; 59 int ret = read(b); 60 if (ret < 0) 61 return ret; 62 return b[0] & 0xFF; 63 } 64 65 @Override 66 public int read(byte[] b, int off, int len) throws IOException { 67 68 if (_loopcount != 0) { 69 int bloopend = _loopend * framesize; 70 int bloopstart = _loopstart * framesize; 71 int pos = _frameposition * framesize; 72 73 if (pos + len >= bloopend) 74 if (pos < bloopend) { 75 int offend = off + len; 76 int o = off; 77 while (off != offend) { 78 if (pos == bloopend) { 79 if (_loopcount == 0) 80 break; 81 pos = bloopstart; 82 if (_loopcount != LOOP_CONTINUOUSLY) 83 _loopcount--; 84 } 85 len = offend - off; 86 int left = bloopend - pos; 87 if (len > left) 88 len = left; 89 System.arraycopy(data, pos, b, off, len); 90 off += len; 91 } 92 if (_loopcount == 0) { 93 len = offend - off; 94 int left = bloopend - pos; 95 if (len > left) 96 len = left; 97 System.arraycopy(data, pos, b, off, len); 98 off += len; 99 } 100 _frameposition = pos / framesize; 101 return o - off; 102 } 103 } 104 105 int pos = _frameposition * framesize; 106 int left = bufferSize - pos; 107 if (left == 0) 108 return -1; 109 if (len > left) 110 len = left; 111 System.arraycopy(data, pos, b, off, len); 112 _frameposition += len / framesize; 113 return len; 114 } 115 116 }; 117 118 private int offset; 119 120 private int bufferSize; 121 122 private float[] readbuffer; 123 124 private boolean open = false; 125 126 private AudioFormat outputformat; 127 128 private int out_nrofchannels; 129 130 private int in_nrofchannels; 131 132 private int frameposition = 0; 133 134 private boolean frameposition_sg = false; 135 136 private boolean active_sg = false; 137 138 private int loopstart = 0; 139 140 private int loopend = -1; 141 142 private boolean active = false; 143 144 private int loopcount = 0; 145 146 private boolean _active = false; 147 148 private int _frameposition = 0; 149 150 private boolean loop_sg = false; 151 152 private int _loopcount = 0; 153 154 private int _loopstart = 0; 155 156 private int _loopend = -1; 157 158 private float _rightgain; 159 160 private float _leftgain; 161 162 private float _eff1gain; 163 164 private float _eff2gain; 165 166 private AudioFloatInputStream afis; 167 168 SoftMixingClip(SoftMixingMixer mixer, DataLine.Info info) { 169 super(mixer, info); 170 } 171 172 @Override 173 protected void processControlLogic() { 174 175 _rightgain = rightgain; 176 _leftgain = leftgain; 177 _eff1gain = eff1gain; 178 _eff2gain = eff2gain; 179 180 if (active_sg) { 181 _active = active; 182 active_sg = false; 183 } else { 184 active = _active; 185 } 186 187 if (frameposition_sg) { 188 _frameposition = frameposition; 189 frameposition_sg = false; 190 afis = null; 191 } else { 192 frameposition = _frameposition; 193 } 194 if (loop_sg) { 195 _loopcount = loopcount; 196 _loopstart = loopstart; 197 _loopend = loopend; 198 } 199 200 if (afis == null) { 201 afis = AudioFloatInputStream.getInputStream(new AudioInputStream( 202 datastream, format, AudioSystem.NOT_SPECIFIED)); 203 204 if (Math.abs(format.getSampleRate() - outputformat.getSampleRate()) > 0.000001) 205 afis = new AudioFloatInputStreamResampler(afis, outputformat); 206 } 207 208 } 209 210 @Override 211 protected void processAudioLogic(SoftAudioBuffer[] buffers) { 212 if (_active) { 213 float[] left = buffers[SoftMixingMainMixer.CHANNEL_LEFT].array(); 214 float[] right = buffers[SoftMixingMainMixer.CHANNEL_RIGHT].array(); 215 int bufferlen = buffers[SoftMixingMainMixer.CHANNEL_LEFT].getSize(); 216 217 int readlen = bufferlen * in_nrofchannels; 218 if (readbuffer == null || readbuffer.length < readlen) { 219 readbuffer = new float[readlen]; 220 } 221 int ret = 0; 222 try { 223 ret = afis.read(readbuffer); 224 if (ret == -1) { 225 _active = false; 226 return; 227 } 228 if (ret != in_nrofchannels) 229 Arrays.fill(readbuffer, ret, readlen, 0); 230 } catch (IOException e) { 231 } 232 233 int in_c = in_nrofchannels; 234 for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) { 235 left[i] += readbuffer[ix] * _leftgain; 236 } 237 238 if (out_nrofchannels != 1) { 239 if (in_nrofchannels == 1) { 240 for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) { 241 right[i] += readbuffer[ix] * _rightgain; 242 } 243 } else { 244 for (int i = 0, ix = 1; i < bufferlen; i++, ix += in_c) { 245 right[i] += readbuffer[ix] * _rightgain; 246 } 247 } 248 249 } 250 251 if (_eff1gain > 0.0002) { 252 253 float[] eff1 = buffers[SoftMixingMainMixer.CHANNEL_EFFECT1] 254 .array(); 255 for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) { 256 eff1[i] += readbuffer[ix] * _eff1gain; 257 } 258 if (in_nrofchannels == 2) { 259 for (int i = 0, ix = 1; i < bufferlen; i++, ix += in_c) { 260 eff1[i] += readbuffer[ix] * _eff1gain; 261 } 262 } 263 } 264 265 if (_eff2gain > 0.0002) { 266 float[] eff2 = buffers[SoftMixingMainMixer.CHANNEL_EFFECT2] 267 .array(); 268 for (int i = 0, ix = 0; i < bufferlen; i++, ix += in_c) { 269 eff2[i] += readbuffer[ix] * _eff2gain; 270 } 271 if (in_nrofchannels == 2) { 272 for (int i = 0, ix = 1; i < bufferlen; i++, ix += in_c) { 273 eff2[i] += readbuffer[ix] * _eff2gain; 274 } 275 } 276 } 277 278 } 279 } 280 281 @Override 282 public int getFrameLength() { 283 return bufferSize / format.getFrameSize(); 284 } 285 286 @Override 287 public long getMicrosecondLength() { 288 return (long) (getFrameLength() * (1000000.0 / (double) getFormat() 289 .getSampleRate())); 290 } 291 292 @Override 293 public void loop(int count) { 294 LineEvent event = null; 295 296 synchronized (control_mutex) { 297 if (isOpen()) { 298 if (active) 299 return; 300 active = true; 301 active_sg = true; 302 loopcount = count; 303 event = new LineEvent(this, LineEvent.Type.START, 304 getLongFramePosition()); 305 } 306 } 307 308 if (event != null) 309 sendEvent(event); 310 311 } 312 313 @Override 314 public void open(AudioInputStream stream) throws LineUnavailableException, 315 IOException { 316 if (isOpen()) { 317 throw new IllegalStateException("Clip is already open with format " 318 + getFormat() + " and frame lengh of " + getFrameLength()); 319 } 320 if (AudioFloatConverter.getConverter(stream.getFormat()) == null) 321 throw new IllegalArgumentException("Invalid format : " 322 + stream.getFormat().toString()); 323 324 if (stream.getFrameLength() != AudioSystem.NOT_SPECIFIED) { 325 byte[] data = new byte[(int) stream.getFrameLength() 326 * stream.getFormat().getFrameSize()]; 327 int readsize = 512 * stream.getFormat().getFrameSize(); 328 int len = 0; 329 while (len != data.length) { 330 if (readsize > data.length - len) 331 readsize = data.length - len; 332 int ret = stream.read(data, len, readsize); 333 if (ret == -1) 334 break; 335 if (ret == 0) 336 Thread.yield(); 337 len += ret; 338 } 339 open(stream.getFormat(), data, 0, len); 340 } else { 341 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 342 byte[] b = new byte[512 * stream.getFormat().getFrameSize()]; 343 int r = 0; 344 while ((r = stream.read(b)) != -1) { 345 if (r == 0) 346 Thread.yield(); 347 baos.write(b, 0, r); 348 } 349 open(stream.getFormat(), baos.toByteArray(), 0, baos.size()); 350 } 351 352 } 353 354 @Override 355 public void open(AudioFormat format, byte[] data, int offset, int bufferSize) 356 throws LineUnavailableException { 357 synchronized (control_mutex) { 358 if (isOpen()) { 359 throw new IllegalStateException( 360 "Clip is already open with format " + getFormat() 361 + " and frame lengh of " + getFrameLength()); 362 } 363 if (AudioFloatConverter.getConverter(format) == null) 364 throw new IllegalArgumentException("Invalid format : " 365 + format.toString()); 366 if (bufferSize % format.getFrameSize() != 0) 367 throw new IllegalArgumentException( 368 "Buffer size does not represent an integral number of sample frames!"); 369 370 if (data != null) { 371 this.data = Arrays.copyOf(data, data.length); 372 } 373 this.offset = offset; 374 this.bufferSize = bufferSize; 375 this.format = format; 376 this.framesize = format.getFrameSize(); 377 378 loopstart = 0; 379 loopend = -1; 380 loop_sg = true; 381 382 if (!mixer.isOpen()) { 383 mixer.open(); 384 mixer.implicitOpen = true; 385 } 386 387 outputformat = mixer.getFormat(); 388 out_nrofchannels = outputformat.getChannels(); 389 in_nrofchannels = format.getChannels(); 390 391 open = true; 392 393 mixer.getMainMixer().openLine(this); 394 } 395 396 } 397 398 @Override 399 public void setFramePosition(int frames) { 400 synchronized (control_mutex) { 401 frameposition_sg = true; 402 frameposition = frames; 403 } 404 } 405 406 @Override 407 public void setLoopPoints(int start, int end) { 408 synchronized (control_mutex) { 409 if (end != -1) { 410 if (end < start) 411 throw new IllegalArgumentException("Invalid loop points : " 412 + start + " - " + end); 413 if (end * framesize > bufferSize) 414 throw new IllegalArgumentException("Invalid loop points : " 415 + start + " - " + end); 416 } 417 if (start * framesize > bufferSize) 418 throw new IllegalArgumentException("Invalid loop points : " 419 + start + " - " + end); 420 if (0 < start) 421 throw new IllegalArgumentException("Invalid loop points : " 422 + start + " - " + end); 423 loopstart = start; 424 loopend = end; 425 loop_sg = true; 426 } 427 } 428 429 @Override 430 public void setMicrosecondPosition(long microseconds) { 431 setFramePosition((int) (microseconds * (((double) getFormat() 432 .getSampleRate()) / 1000000.0))); 433 } 434 435 @Override 436 public int available() { 437 return 0; 438 } 439 440 @Override 441 public void drain() { 442 } 443 444 @Override 445 public void flush() { 446 } 447 448 @Override 449 public int getBufferSize() { 450 return bufferSize; 451 } 452 453 @Override 454 public AudioFormat getFormat() { 455 return format; 456 } 457 458 @Override 459 public int getFramePosition() { 460 synchronized (control_mutex) { 461 return frameposition; 462 } 463 } 464 465 @Override 466 public float getLevel() { 467 return AudioSystem.NOT_SPECIFIED; 468 } 469 470 @Override 471 public long getLongFramePosition() { 472 return getFramePosition(); 473 } 474 475 @Override 476 public long getMicrosecondPosition() { 477 return (long) (getFramePosition() * (1000000.0 / (double) getFormat() 478 .getSampleRate())); 479 } 480 481 @Override 482 public boolean isActive() { 483 synchronized (control_mutex) { 484 return active; 485 } 486 } 487 488 @Override 489 public boolean isRunning() { 490 synchronized (control_mutex) { 491 return active; 492 } 493 } 494 495 @Override 496 public void start() { 497 498 LineEvent event = null; 499 500 synchronized (control_mutex) { 501 if (isOpen()) { 502 if (active) 503 return; 504 active = true; 505 active_sg = true; 506 loopcount = 0; 507 event = new LineEvent(this, LineEvent.Type.START, 508 getLongFramePosition()); 509 } 510 } 511 512 if (event != null) 513 sendEvent(event); 514 } 515 516 @Override 517 public void stop() { 518 LineEvent event = null; 519 520 synchronized (control_mutex) { 521 if (isOpen()) { 522 if (!active) 523 return; 524 active = false; 525 active_sg = true; 526 event = new LineEvent(this, LineEvent.Type.STOP, 527 getLongFramePosition()); 528 } 529 } 530 531 if (event != null) 532 sendEvent(event); 533 } 534 535 @Override 536 public void close() { 537 LineEvent event = null; 538 539 synchronized (control_mutex) { 540 if (!isOpen()) 541 return; 542 stop(); 543 544 event = new LineEvent(this, LineEvent.Type.CLOSE, 545 getLongFramePosition()); 546 547 open = false; 548 mixer.getMainMixer().closeLine(this); 549 } 550 551 if (event != null) 552 sendEvent(event); 553 554 } 555 556 @Override 557 public boolean isOpen() { 558 return open; 559 } 560 561 @Override 562 public void open() throws LineUnavailableException { 563 if (data == null) { 564 throw new IllegalArgumentException( 565 "Illegal call to open() in interface Clip"); 566 } 567 open(format, data, offset, bufferSize); 568 } 569 570 }