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