1/* 2 * Copyright (c) 2008, 2016, 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 26package com.sun.media.sound; 27 28import java.io.ByteArrayOutputStream; 29import java.io.IOException; 30import java.io.InputStream; 31import java.util.Arrays; 32 33import javax.sound.sampled.AudioFormat; 34import javax.sound.sampled.AudioInputStream; 35import javax.sound.sampled.AudioSystem; 36import javax.sound.sampled.Clip; 37import javax.sound.sampled.DataLine; 38import javax.sound.sampled.LineEvent; 39import javax.sound.sampled.LineUnavailableException; 40 41/** 42 * Clip implementation for the SoftMixingMixer. 43 * 44 * @author Karl Helgason 45 */ 46public 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 Toolkit.validateBuffer(format.getFrameSize(), bufferSize); 367 368 if (data != null) { 369 this.data = Arrays.copyOf(data, data.length); 370 } 371 this.offset = offset; 372 this.bufferSize = bufferSize; 373 this.format = format; 374 this.framesize = format.getFrameSize(); 375 376 loopstart = 0; 377 loopend = -1; 378 loop_sg = true; 379 380 if (!mixer.isOpen()) { 381 mixer.open(); 382 mixer.implicitOpen = true; 383 } 384 385 outputformat = mixer.getFormat(); 386 out_nrofchannels = outputformat.getChannels(); 387 in_nrofchannels = format.getChannels(); 388 389 open = true; 390 391 mixer.getMainMixer().openLine(this); 392 } 393 394 } 395 396 @Override 397 public void setFramePosition(int frames) { 398 synchronized (control_mutex) { 399 frameposition_sg = true; 400 frameposition = frames; 401 } 402 } 403 404 @Override 405 public void setLoopPoints(int start, int end) { 406 synchronized (control_mutex) { 407 if (end != -1) { 408 if (end < start) 409 throw new IllegalArgumentException("Invalid loop points : " 410 + start + " - " + end); 411 if (end * framesize > bufferSize) 412 throw new IllegalArgumentException("Invalid loop points : " 413 + start + " - " + end); 414 } 415 if (start * framesize > bufferSize) 416 throw new IllegalArgumentException("Invalid loop points : " 417 + start + " - " + end); 418 if (0 < start) 419 throw new IllegalArgumentException("Invalid loop points : " 420 + start + " - " + end); 421 loopstart = start; 422 loopend = end; 423 loop_sg = true; 424 } 425 } 426 427 @Override 428 public void setMicrosecondPosition(long microseconds) { 429 setFramePosition((int) (microseconds * (((double) getFormat() 430 .getSampleRate()) / 1000000.0))); 431 } 432 433 @Override 434 public int available() { 435 return 0; 436 } 437 438 @Override 439 public void drain() { 440 } 441 442 @Override 443 public void flush() { 444 } 445 446 @Override 447 public int getBufferSize() { 448 return bufferSize; 449 } 450 451 @Override 452 public AudioFormat getFormat() { 453 return format; 454 } 455 456 @Override 457 public int getFramePosition() { 458 synchronized (control_mutex) { 459 return frameposition; 460 } 461 } 462 463 @Override 464 public float getLevel() { 465 return AudioSystem.NOT_SPECIFIED; 466 } 467 468 @Override 469 public long getLongFramePosition() { 470 return getFramePosition(); 471 } 472 473 @Override 474 public long getMicrosecondPosition() { 475 return (long) (getFramePosition() * (1000000.0 / (double) getFormat() 476 .getSampleRate())); 477 } 478 479 @Override 480 public boolean isActive() { 481 synchronized (control_mutex) { 482 return active; 483 } 484 } 485 486 @Override 487 public boolean isRunning() { 488 synchronized (control_mutex) { 489 return active; 490 } 491 } 492 493 @Override 494 public void start() { 495 496 LineEvent event = null; 497 498 synchronized (control_mutex) { 499 if (isOpen()) { 500 if (active) 501 return; 502 active = true; 503 active_sg = true; 504 loopcount = 0; 505 event = new LineEvent(this, LineEvent.Type.START, 506 getLongFramePosition()); 507 } 508 } 509 510 if (event != null) 511 sendEvent(event); 512 } 513 514 @Override 515 public void stop() { 516 LineEvent event = null; 517 518 synchronized (control_mutex) { 519 if (isOpen()) { 520 if (!active) 521 return; 522 active = false; 523 active_sg = true; 524 event = new LineEvent(this, LineEvent.Type.STOP, 525 getLongFramePosition()); 526 } 527 } 528 529 if (event != null) 530 sendEvent(event); 531 } 532 533 @Override 534 public void close() { 535 LineEvent event = null; 536 537 synchronized (control_mutex) { 538 if (!isOpen()) 539 return; 540 stop(); 541 542 event = new LineEvent(this, LineEvent.Type.CLOSE, 543 getLongFramePosition()); 544 545 open = false; 546 mixer.getMainMixer().closeLine(this); 547 } 548 549 if (event != null) 550 sendEvent(event); 551 552 } 553 554 @Override 555 public boolean isOpen() { 556 return open; 557 } 558 559 @Override 560 public void open() throws LineUnavailableException { 561 if (data == null) { 562 throw new IllegalArgumentException( 563 "Illegal call to open() in interface Clip"); 564 } 565 open(format, data, offset, bufferSize); 566 } 567 568} 569