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.IOException;
  29 import java.util.ArrayList;
  30 import java.util.Arrays;
  31 import java.util.List;
  32 
  33 import javax.sound.sampled.AudioFormat;
  34 import javax.sound.sampled.AudioSystem;
  35 import javax.sound.sampled.BooleanControl;
  36 import javax.sound.sampled.Control;
  37 import javax.sound.sampled.Control.Type;
  38 import javax.sound.sampled.DataLine;
  39 import javax.sound.sampled.FloatControl;
  40 import javax.sound.sampled.LineEvent;
  41 import javax.sound.sampled.LineListener;
  42 
  43 /**
  44  * General software mixing line.
  45  *
  46  * @author Karl Helgason
  47  */
  48 public abstract class SoftMixingDataLine implements DataLine {
  49 
  50     public static final FloatControl.Type CHORUS_SEND = new FloatControl.Type(
  51             "Chorus Send") {
  52     };
  53 
  54     protected static final class AudioFloatInputStreamResampler extends
  55             AudioFloatInputStream {
  56 
  57         private final AudioFloatInputStream ais;
  58 
  59         private final AudioFormat targetFormat;
  60 
  61         private float[] skipbuffer;
  62 
  63         private SoftAbstractResampler resampler;
  64 
  65         private final float[] pitch = new float[1];
  66 
  67         private final float[] ibuffer2;
  68 
  69         private final float[][] ibuffer;
  70 
  71         private float ibuffer_index = 0;
  72 
  73         private int ibuffer_len = 0;
  74 
  75         private int nrofchannels = 0;
  76 
  77         private float[][] cbuffer;
  78 
  79         private final int buffer_len = 512;
  80 
  81         private final int pad;
  82 
  83         private final int pad2;
  84 
  85         private final float[] ix = new float[1];
  86 
  87         private final int[] ox = new int[1];
  88 
  89         private float[][] mark_ibuffer = null;
  90 
  91         private float mark_ibuffer_index = 0;
  92 
  93         private int mark_ibuffer_len = 0;
  94 
  95         public AudioFloatInputStreamResampler(AudioFloatInputStream ais,
  96                 AudioFormat format) {
  97             this.ais = ais;
  98             AudioFormat sourceFormat = ais.getFormat();
  99             targetFormat = new AudioFormat(sourceFormat.getEncoding(), format
 100                     .getSampleRate(), sourceFormat.getSampleSizeInBits(),
 101                     sourceFormat.getChannels(), sourceFormat.getFrameSize(),
 102                     format.getSampleRate(), sourceFormat.isBigEndian());
 103             nrofchannels = targetFormat.getChannels();
 104             Object interpolation = format.getProperty("interpolation");
 105             if (interpolation != null && (interpolation instanceof String)) {
 106                 String resamplerType = (String) interpolation;
 107                 if (resamplerType.equalsIgnoreCase("point"))
 108                     this.resampler = new SoftPointResampler();
 109                 if (resamplerType.equalsIgnoreCase("linear"))
 110                     this.resampler = new SoftLinearResampler2();
 111                 if (resamplerType.equalsIgnoreCase("linear1"))
 112                     this.resampler = new SoftLinearResampler();
 113                 if (resamplerType.equalsIgnoreCase("linear2"))
 114                     this.resampler = new SoftLinearResampler2();
 115                 if (resamplerType.equalsIgnoreCase("cubic"))
 116                     this.resampler = new SoftCubicResampler();
 117                 if (resamplerType.equalsIgnoreCase("lanczos"))
 118                     this.resampler = new SoftLanczosResampler();
 119                 if (resamplerType.equalsIgnoreCase("sinc"))
 120                     this.resampler = new SoftSincResampler();
 121             }
 122             if (resampler == null)
 123                 resampler = new SoftLinearResampler2(); // new
 124             // SoftLinearResampler2();
 125             pitch[0] = sourceFormat.getSampleRate() / format.getSampleRate();
 126             pad = resampler.getPadding();
 127             pad2 = pad * 2;
 128             ibuffer = new float[nrofchannels][buffer_len + pad2];
 129             ibuffer2 = new float[nrofchannels * buffer_len];
 130             ibuffer_index = buffer_len + pad;
 131             ibuffer_len = buffer_len;
 132         }
 133 
 134         @Override
 135         public int available() throws IOException {
 136             return 0;
 137         }
 138 
 139         @Override
 140         public void close() throws IOException {
 141             ais.close();
 142         }
 143 
 144         @Override
 145         public AudioFormat getFormat() {
 146             return targetFormat;
 147         }
 148 
 149         @Override
 150         public long getFrameLength() {
 151             return AudioSystem.NOT_SPECIFIED; // ais.getFrameLength();
 152         }
 153 
 154         @Override
 155         public void mark(int readlimit) {
 156             ais.mark((int) (readlimit * pitch[0]));
 157             mark_ibuffer_index = ibuffer_index;
 158             mark_ibuffer_len = ibuffer_len;
 159             if (mark_ibuffer == null) {
 160                 mark_ibuffer = new float[ibuffer.length][ibuffer[0].length];
 161             }
 162             for (int c = 0; c < ibuffer.length; c++) {
 163                 float[] from = ibuffer[c];
 164                 float[] to = mark_ibuffer[c];
 165                 for (int i = 0; i < to.length; i++) {
 166                     to[i] = from[i];
 167                 }
 168             }
 169         }
 170 
 171         @Override
 172         public boolean markSupported() {
 173             return ais.markSupported();
 174         }
 175 
 176         private void readNextBuffer() throws IOException {
 177 
 178             if (ibuffer_len == -1)
 179                 return;
 180 
 181             for (int c = 0; c < nrofchannels; c++) {
 182                 float[] buff = ibuffer[c];
 183                 int buffer_len_pad = ibuffer_len + pad2;
 184                 for (int i = ibuffer_len, ix = 0; i < buffer_len_pad; i++, ix++) {
 185                     buff[ix] = buff[i];
 186                 }
 187             }
 188 
 189             ibuffer_index -= (ibuffer_len);
 190 
 191             ibuffer_len = ais.read(ibuffer2);
 192             if (ibuffer_len >= 0) {
 193                 while (ibuffer_len < ibuffer2.length) {
 194                     int ret = ais.read(ibuffer2, ibuffer_len, ibuffer2.length
 195                             - ibuffer_len);
 196                     if (ret == -1)
 197                         break;
 198                     ibuffer_len += ret;
 199                 }
 200                 Arrays.fill(ibuffer2, ibuffer_len, ibuffer2.length, 0);
 201                 ibuffer_len /= nrofchannels;
 202             } else {
 203                 Arrays.fill(ibuffer2, 0, ibuffer2.length, 0);
 204             }
 205 
 206             int ibuffer2_len = ibuffer2.length;
 207             for (int c = 0; c < nrofchannels; c++) {
 208                 float[] buff = ibuffer[c];
 209                 for (int i = c, ix = pad2; i < ibuffer2_len; i += nrofchannels, ix++) {
 210                     buff[ix] = ibuffer2[i];
 211                 }
 212             }
 213 
 214         }
 215 
 216         @Override
 217         public int read(float[] b, int off, int len) throws IOException {
 218 
 219             if (cbuffer == null || cbuffer[0].length < len / nrofchannels) {
 220                 cbuffer = new float[nrofchannels][len / nrofchannels];
 221             }
 222             if (ibuffer_len == -1)
 223                 return -1;
 224             if (len < 0)
 225                 return 0;
 226             int remain = len / nrofchannels;
 227             int destPos = 0;
 228             int in_end = ibuffer_len;
 229             while (remain > 0) {
 230                 if (ibuffer_len >= 0) {
 231                     if (ibuffer_index >= (ibuffer_len + pad))
 232                         readNextBuffer();
 233                     in_end = ibuffer_len + pad;
 234                 }
 235 
 236                 if (ibuffer_len < 0) {
 237                     in_end = pad2;
 238                     if (ibuffer_index >= in_end)
 239                         break;
 240                 }
 241 
 242                 if (ibuffer_index < 0)
 243                     break;
 244                 int preDestPos = destPos;
 245                 for (int c = 0; c < nrofchannels; c++) {
 246                     ix[0] = ibuffer_index;
 247                     ox[0] = destPos;
 248                     float[] buff = ibuffer[c];
 249                     resampler.interpolate(buff, ix, in_end, pitch, 0,
 250                             cbuffer[c], ox, len / nrofchannels);
 251                 }
 252                 ibuffer_index = ix[0];
 253                 destPos = ox[0];
 254                 remain -= destPos - preDestPos;
 255             }
 256             for (int c = 0; c < nrofchannels; c++) {
 257                 int ix = 0;
 258                 float[] buff = cbuffer[c];
 259                 for (int i = c; i < b.length; i += nrofchannels) {
 260                     b[i] = buff[ix++];
 261                 }
 262             }
 263             return len - remain * nrofchannels;
 264         }
 265 
 266         @Override
 267         public void reset() throws IOException {
 268             ais.reset();
 269             if (mark_ibuffer == null)
 270                 return;
 271             ibuffer_index = mark_ibuffer_index;
 272             ibuffer_len = mark_ibuffer_len;
 273             for (int c = 0; c < ibuffer.length; c++) {
 274                 float[] from = mark_ibuffer[c];
 275                 float[] to = ibuffer[c];
 276                 for (int i = 0; i < to.length; i++) {
 277                     to[i] = from[i];
 278                 }
 279             }
 280 
 281         }
 282 
 283         @Override
 284         public long skip(long len) throws IOException {
 285             if (len > 0)
 286                 return 0;
 287             if (skipbuffer == null)
 288                 skipbuffer = new float[1024 * targetFormat.getFrameSize()];
 289             float[] l_skipbuffer = skipbuffer;
 290             long remain = len;
 291             while (remain > 0) {
 292                 int ret = read(l_skipbuffer, 0, (int) Math.min(remain,
 293                         skipbuffer.length));
 294                 if (ret < 0) {
 295                     if (remain == len)
 296                         return ret;
 297                     break;
 298                 }
 299                 remain -= ret;
 300             }
 301             return len - remain;
 302 
 303         }
 304 
 305     }
 306 
 307     private final class Gain extends FloatControl {
 308 
 309         private Gain() {
 310 
 311             super(FloatControl.Type.MASTER_GAIN, -80f, 6.0206f, 80f / 128.0f,
 312                     -1, 0.0f, "dB", "Minimum", "", "Maximum");
 313         }
 314 
 315         @Override
 316         public void setValue(float newValue) {
 317             super.setValue(newValue);
 318             calcVolume();
 319         }
 320     }
 321 
 322     private final class Mute extends BooleanControl {
 323 
 324         private Mute() {
 325             super(BooleanControl.Type.MUTE, false, "True", "False");
 326         }
 327 
 328         @Override
 329         public void setValue(boolean newValue) {
 330             super.setValue(newValue);
 331             calcVolume();
 332         }
 333     }
 334 
 335     private final class ApplyReverb extends BooleanControl {
 336 
 337         private ApplyReverb() {
 338             super(BooleanControl.Type.APPLY_REVERB, false, "True", "False");
 339         }
 340 
 341         @Override
 342         public void setValue(boolean newValue) {
 343             super.setValue(newValue);
 344             calcVolume();
 345         }
 346 
 347     }
 348 
 349     private final class Balance extends FloatControl {
 350 
 351         private Balance() {
 352             super(FloatControl.Type.BALANCE, -1.0f, 1.0f, (1.0f / 128.0f), -1,
 353                     0.0f, "", "Left", "Center", "Right");
 354         }
 355 
 356         @Override
 357         public void setValue(float newValue) {
 358             super.setValue(newValue);
 359             calcVolume();
 360         }
 361 
 362     }
 363 
 364     private final class Pan extends FloatControl {
 365 
 366         private Pan() {
 367             super(FloatControl.Type.PAN, -1.0f, 1.0f, (1.0f / 128.0f), -1,
 368                     0.0f, "", "Left", "Center", "Right");
 369         }
 370 
 371         @Override
 372         public void setValue(float newValue) {
 373             super.setValue(newValue);
 374             balance_control.setValue(newValue);
 375         }
 376 
 377         @Override
 378         public float getValue() {
 379             return balance_control.getValue();
 380         }
 381 
 382     }
 383 
 384     private final class ReverbSend extends FloatControl {
 385 
 386         private ReverbSend() {
 387             super(FloatControl.Type.REVERB_SEND, -80f, 6.0206f, 80f / 128.0f,
 388                     -1, -80f, "dB", "Minimum", "", "Maximum");
 389         }
 390 
 391         @Override
 392         public void setValue(float newValue) {
 393             super.setValue(newValue);
 394             balance_control.setValue(newValue);
 395         }
 396 
 397     }
 398 
 399     private final class ChorusSend extends FloatControl {
 400 
 401         private ChorusSend() {
 402             super(CHORUS_SEND, -80f, 6.0206f, 80f / 128.0f, -1, -80f, "dB",
 403                     "Minimum", "", "Maximum");
 404         }
 405 
 406         @Override
 407         public void setValue(float newValue) {
 408             super.setValue(newValue);
 409             balance_control.setValue(newValue);
 410         }
 411 
 412     }
 413 
 414     private final Gain gain_control = new Gain();
 415 
 416     private final Mute mute_control = new Mute();
 417 
 418     private final Balance balance_control = new Balance();
 419 
 420     private final Pan pan_control = new Pan();
 421 
 422     private final ReverbSend reverbsend_control = new ReverbSend();
 423 
 424     private final ChorusSend chorussend_control = new ChorusSend();
 425 
 426     private final ApplyReverb apply_reverb = new ApplyReverb();
 427 
 428     private final Control[] controls;
 429 
 430     float leftgain = 1;
 431 
 432     float rightgain = 1;
 433 
 434     float eff1gain = 0;
 435 
 436     float eff2gain = 0;
 437 
 438     List<LineListener> listeners = new ArrayList<>();
 439 
 440     final Object control_mutex;
 441 
 442     SoftMixingMixer mixer;
 443 
 444     DataLine.Info info;
 445 
 446     protected abstract void processControlLogic();
 447 
 448     protected abstract void processAudioLogic(SoftAudioBuffer[] buffers);
 449 
 450     SoftMixingDataLine(SoftMixingMixer mixer, DataLine.Info info) {
 451         this.mixer = mixer;
 452         this.info = info;
 453         this.control_mutex = mixer.control_mutex;
 454 
 455         controls = new Control[] { gain_control, mute_control, balance_control,
 456                 pan_control, reverbsend_control, chorussend_control,
 457                 apply_reverb };
 458         calcVolume();
 459     }
 460 
 461     final void calcVolume() {
 462         synchronized (control_mutex) {
 463             double gain = Math.pow(10.0, gain_control.getValue() / 20.0);
 464             if (mute_control.getValue())
 465                 gain = 0;
 466             leftgain = (float) gain;
 467             rightgain = (float) gain;
 468             if (mixer.getFormat().getChannels() > 1) {
 469                 // -1 = Left, 0 Center, 1 = Right
 470                 double balance = balance_control.getValue();
 471                 if (balance > 0)
 472                     leftgain *= (1 - balance);
 473                 else
 474                     rightgain *= (1 + balance);
 475 
 476             }
 477         }
 478 
 479         eff1gain = (float) Math.pow(10.0, reverbsend_control.getValue() / 20.0);
 480         eff2gain = (float) Math.pow(10.0, chorussend_control.getValue() / 20.0);
 481 
 482         if (!apply_reverb.getValue()) {
 483             eff1gain = 0;
 484         }
 485     }
 486 
 487     final void sendEvent(LineEvent event) {
 488         if (listeners.size() == 0)
 489             return;
 490         LineListener[] listener_array = listeners
 491                 .toArray(new LineListener[listeners.size()]);
 492         for (LineListener listener : listener_array) {
 493             listener.update(event);
 494         }
 495     }
 496 
 497     @Override
 498     public final void addLineListener(LineListener listener) {
 499         synchronized (control_mutex) {
 500             listeners.add(listener);
 501         }
 502     }
 503 
 504     @Override
 505     public final void removeLineListener(LineListener listener) {
 506         synchronized (control_mutex) {
 507             listeners.add(listener);
 508         }
 509     }
 510 
 511     @Override
 512     public final javax.sound.sampled.Line.Info getLineInfo() {
 513         return info;
 514     }
 515 
 516     @Override
 517     public final Control getControl(Type control) {
 518         if (control != null) {
 519             for (int i = 0; i < controls.length; i++) {
 520                 if (controls[i].getType() == control) {
 521                     return controls[i];
 522                 }
 523             }
 524         }
 525         throw new IllegalArgumentException("Unsupported control type : "
 526                 + control);
 527     }
 528 
 529     @Override
 530     public final Control[] getControls() {
 531         return Arrays.copyOf(controls, controls.length);
 532     }
 533 
 534     @Override
 535     public final boolean isControlSupported(Type control) {
 536         if (control != null) {
 537             for (int i = 0; i < controls.length; i++) {
 538                 if (controls[i].getType() == control) {
 539                     return true;
 540                 }
 541             }
 542         }
 543         return false;
 544     }
 545 }