Save This Page
Home » openjdk-7 » com.sun.media » sound » [javadoc | source]
    1   /*
    2    * Copyright (c) 2008, 2010, 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.IOException;
   28   import java.io.InputStream;
   29   import java.util.ArrayList;
   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.AudioFormat.Encoding;
   36   import javax.sound.sampled.spi.FormatConversionProvider;
   37   
   38   /**
   39    * This class is used to convert between 8,16,24,32 bit signed/unsigned
   40    * big/litle endian fixed/floating stereo/mono/multi-channel audio streams and
   41    * perform sample-rate conversion if needed.
   42    *
   43    * @author Karl Helgason
   44    */
   45   public class AudioFloatFormatConverter extends FormatConversionProvider {
   46   
   47       private static class AudioFloatFormatConverterInputStream extends
   48               InputStream {
   49           private AudioFloatConverter converter;
   50   
   51           private AudioFloatInputStream stream;
   52   
   53           private float[] readfloatbuffer;
   54   
   55           private int fsize = 0;
   56   
   57           public AudioFloatFormatConverterInputStream(AudioFormat targetFormat,
   58                   AudioFloatInputStream stream) {
   59               this.stream = stream;
   60               converter = AudioFloatConverter.getConverter(targetFormat);
   61               fsize = ((targetFormat.getSampleSizeInBits() + 7) / 8);
   62           }
   63   
   64           public int read() throws IOException {
   65               byte[] b = new byte[1];
   66               int ret = read(b);
   67               if (ret < 0)
   68                   return ret;
   69               return b[0] & 0xFF;
   70           }
   71   
   72           public int read(byte[] b, int off, int len) throws IOException {
   73   
   74               int flen = len / fsize;
   75               if (readfloatbuffer == null || readfloatbuffer.length < flen)
   76                   readfloatbuffer = new float[flen];
   77               int ret = stream.read(readfloatbuffer, 0, flen);
   78               if (ret < 0)
   79                   return ret;
   80               converter.toByteArray(readfloatbuffer, 0, ret, b, off);
   81               return ret * fsize;
   82           }
   83   
   84           public int available() throws IOException {
   85               int ret = stream.available();
   86               if (ret < 0)
   87                   return ret;
   88               return ret * fsize;
   89           }
   90   
   91           public void close() throws IOException {
   92               stream.close();
   93           }
   94   
   95           public synchronized void mark(int readlimit) {
   96               stream.mark(readlimit * fsize);
   97           }
   98   
   99           public boolean markSupported() {
  100               return stream.markSupported();
  101           }
  102   
  103           public synchronized void reset() throws IOException {
  104               stream.reset();
  105           }
  106   
  107           public long skip(long n) throws IOException {
  108               long ret = stream.skip(n / fsize);
  109               if (ret < 0)
  110                   return ret;
  111               return ret * fsize;
  112           }
  113   
  114       }
  115   
  116       private static class AudioFloatInputStreamChannelMixer extends
  117               AudioFloatInputStream {
  118   
  119           private int targetChannels;
  120   
  121           private int sourceChannels;
  122   
  123           private AudioFloatInputStream ais;
  124   
  125           private AudioFormat targetFormat;
  126   
  127           private float[] conversion_buffer;
  128   
  129           public AudioFloatInputStreamChannelMixer(AudioFloatInputStream ais,
  130                   int targetChannels) {
  131               this.sourceChannels = ais.getFormat().getChannels();
  132               this.targetChannels = targetChannels;
  133               this.ais = ais;
  134               AudioFormat format = ais.getFormat();
  135               targetFormat = new AudioFormat(format.getEncoding(), format
  136                       .getSampleRate(), format.getSampleSizeInBits(),
  137                       targetChannels, (format.getFrameSize() / sourceChannels)
  138                               * targetChannels, format.getFrameRate(), format
  139                               .isBigEndian());
  140           }
  141   
  142           public int available() throws IOException {
  143               return (ais.available() / sourceChannels) * targetChannels;
  144           }
  145   
  146           public void close() throws IOException {
  147               ais.close();
  148           }
  149   
  150           public AudioFormat getFormat() {
  151               return targetFormat;
  152           }
  153   
  154           public long getFrameLength() {
  155               return ais.getFrameLength();
  156           }
  157   
  158           public void mark(int readlimit) {
  159               ais.mark((readlimit / targetChannels) * sourceChannels);
  160           }
  161   
  162           public boolean markSupported() {
  163               return ais.markSupported();
  164           }
  165   
  166           public int read(float[] b, int off, int len) throws IOException {
  167               int len2 = (len / targetChannels) * sourceChannels;
  168               if (conversion_buffer == null || conversion_buffer.length < len2)
  169                   conversion_buffer = new float[len2];
  170               int ret = ais.read(conversion_buffer, 0, len2);
  171               if (ret < 0)
  172                   return ret;
  173               if (sourceChannels == 1) {
  174                   int cs = targetChannels;
  175                   for (int c = 0; c < targetChannels; c++) {
  176                       for (int i = 0, ix = off + c; i < len2; i++, ix += cs) {
  177                           b[ix] = conversion_buffer[i];
  178                       }
  179                   }
  180               } else if (targetChannels == 1) {
  181                   int cs = sourceChannels;
  182                   for (int i = 0, ix = off; i < len2; i += cs, ix++) {
  183                       b[ix] = conversion_buffer[i];
  184                   }
  185                   for (int c = 1; c < sourceChannels; c++) {
  186                       for (int i = c, ix = off; i < len2; i += cs, ix++) {
  187                           b[ix] += conversion_buffer[i];
  188                       }
  189                   }
  190                   float vol = 1f / ((float) sourceChannels);
  191                   for (int i = 0, ix = off; i < len2; i += cs, ix++) {
  192                       b[ix] *= vol;
  193                   }
  194               } else {
  195                   int minChannels = Math.min(sourceChannels, targetChannels);
  196                   int off_len = off + len;
  197                   int ct = targetChannels;
  198                   int cs = sourceChannels;
  199                   for (int c = 0; c < minChannels; c++) {
  200                       for (int i = off + c, ix = c; i < off_len; i += ct, ix += cs) {
  201                           b[i] = conversion_buffer[ix];
  202                       }
  203                   }
  204                   for (int c = minChannels; c < targetChannels; c++) {
  205                       for (int i = off + c; i < off_len; i += ct) {
  206                           b[i] = 0;
  207                       }
  208                   }
  209               }
  210               return (ret / sourceChannels) * targetChannels;
  211           }
  212   
  213           public void reset() throws IOException {
  214               ais.reset();
  215           }
  216   
  217           public long skip(long len) throws IOException {
  218               long ret = ais.skip((len / targetChannels) * sourceChannels);
  219               if (ret < 0)
  220                   return ret;
  221               return (ret / sourceChannels) * targetChannels;
  222           }
  223   
  224       }
  225   
  226       private static class AudioFloatInputStreamResampler extends
  227               AudioFloatInputStream {
  228   
  229           private AudioFloatInputStream ais;
  230   
  231           private AudioFormat targetFormat;
  232   
  233           private float[] skipbuffer;
  234   
  235           private SoftAbstractResampler resampler;
  236   
  237           private float[] pitch = new float[1];
  238   
  239           private float[] ibuffer2;
  240   
  241           private float[][] ibuffer;
  242   
  243           private float ibuffer_index = 0;
  244   
  245           private int ibuffer_len = 0;
  246   
  247           private int nrofchannels = 0;
  248   
  249           private float[][] cbuffer;
  250   
  251           private int buffer_len = 512;
  252   
  253           private int pad;
  254   
  255           private int pad2;
  256   
  257           private float[] ix = new float[1];
  258   
  259           private int[] ox = new int[1];
  260   
  261           private float[][] mark_ibuffer = null;
  262   
  263           private float mark_ibuffer_index = 0;
  264   
  265           private int mark_ibuffer_len = 0;
  266   
  267           public AudioFloatInputStreamResampler(AudioFloatInputStream ais,
  268                   AudioFormat format) {
  269               this.ais = ais;
  270               AudioFormat sourceFormat = ais.getFormat();
  271               targetFormat = new AudioFormat(sourceFormat.getEncoding(), format
  272                       .getSampleRate(), sourceFormat.getSampleSizeInBits(),
  273                       sourceFormat.getChannels(), sourceFormat.getFrameSize(),
  274                       format.getSampleRate(), sourceFormat.isBigEndian());
  275               nrofchannels = targetFormat.getChannels();
  276               Object interpolation = format.getProperty("interpolation");
  277               if (interpolation != null && (interpolation instanceof String)) {
  278                   String resamplerType = (String) interpolation;
  279                   if (resamplerType.equalsIgnoreCase("point"))
  280                       this.resampler = new SoftPointResampler();
  281                   if (resamplerType.equalsIgnoreCase("linear"))
  282                       this.resampler = new SoftLinearResampler2();
  283                   if (resamplerType.equalsIgnoreCase("linear1"))
  284                       this.resampler = new SoftLinearResampler();
  285                   if (resamplerType.equalsIgnoreCase("linear2"))
  286                       this.resampler = new SoftLinearResampler2();
  287                   if (resamplerType.equalsIgnoreCase("cubic"))
  288                       this.resampler = new SoftCubicResampler();
  289                   if (resamplerType.equalsIgnoreCase("lanczos"))
  290                       this.resampler = new SoftLanczosResampler();
  291                   if (resamplerType.equalsIgnoreCase("sinc"))
  292                       this.resampler = new SoftSincResampler();
  293               }
  294               if (resampler == null)
  295                   resampler = new SoftLinearResampler2(); // new
  296                                                           // SoftLinearResampler2();
  297               pitch[0] = sourceFormat.getSampleRate() / format.getSampleRate();
  298               pad = resampler.getPadding();
  299               pad2 = pad * 2;
  300               ibuffer = new float[nrofchannels][buffer_len + pad2];
  301               ibuffer2 = new float[nrofchannels * buffer_len];
  302               ibuffer_index = buffer_len + pad;
  303               ibuffer_len = buffer_len;
  304           }
  305   
  306           public int available() throws IOException {
  307               return 0;
  308           }
  309   
  310           public void close() throws IOException {
  311               ais.close();
  312           }
  313   
  314           public AudioFormat getFormat() {
  315               return targetFormat;
  316           }
  317   
  318           public long getFrameLength() {
  319               return AudioSystem.NOT_SPECIFIED; // ais.getFrameLength();
  320           }
  321   
  322           public void mark(int readlimit) {
  323               ais.mark((int) (readlimit * pitch[0]));
  324               mark_ibuffer_index = ibuffer_index;
  325               mark_ibuffer_len = ibuffer_len;
  326               if (mark_ibuffer == null) {
  327                   mark_ibuffer = new float[ibuffer.length][ibuffer[0].length];
  328               }
  329               for (int c = 0; c < ibuffer.length; c++) {
  330                   float[] from = ibuffer[c];
  331                   float[] to = mark_ibuffer[c];
  332                   for (int i = 0; i < to.length; i++) {
  333                       to[i] = from[i];
  334                   }
  335               }
  336           }
  337   
  338           public boolean markSupported() {
  339               return ais.markSupported();
  340           }
  341   
  342           private void readNextBuffer() throws IOException {
  343   
  344               if (ibuffer_len == -1)
  345                   return;
  346   
  347               for (int c = 0; c < nrofchannels; c++) {
  348                   float[] buff = ibuffer[c];
  349                   int buffer_len_pad = ibuffer_len + pad2;
  350                   for (int i = ibuffer_len, ix = 0; i < buffer_len_pad; i++, ix++) {
  351                       buff[ix] = buff[i];
  352                   }
  353               }
  354   
  355               ibuffer_index -= (ibuffer_len);
  356   
  357               ibuffer_len = ais.read(ibuffer2);
  358               if (ibuffer_len >= 0) {
  359                   while (ibuffer_len < ibuffer2.length) {
  360                       int ret = ais.read(ibuffer2, ibuffer_len, ibuffer2.length
  361                               - ibuffer_len);
  362                       if (ret == -1)
  363                           break;
  364                       ibuffer_len += ret;
  365                   }
  366                   Arrays.fill(ibuffer2, ibuffer_len, ibuffer2.length, 0);
  367                   ibuffer_len /= nrofchannels;
  368               } else {
  369                   Arrays.fill(ibuffer2, 0, ibuffer2.length, 0);
  370               }
  371   
  372               int ibuffer2_len = ibuffer2.length;
  373               for (int c = 0; c < nrofchannels; c++) {
  374                   float[] buff = ibuffer[c];
  375                   for (int i = c, ix = pad2; i < ibuffer2_len; i += nrofchannels, ix++) {
  376                       buff[ix] = ibuffer2[i];
  377                   }
  378               }
  379   
  380           }
  381   
  382           public int read(float[] b, int off, int len) throws IOException {
  383   
  384               if (cbuffer == null || cbuffer[0].length < len / nrofchannels) {
  385                   cbuffer = new float[nrofchannels][len / nrofchannels];
  386               }
  387               if (ibuffer_len == -1)
  388                   return -1;
  389               if (len < 0)
  390                   return 0;
  391               int offlen = off + len;
  392               int remain = len / nrofchannels;
  393               int destPos = 0;
  394               int in_end = ibuffer_len;
  395               while (remain > 0) {
  396                   if (ibuffer_len >= 0) {
  397                       if (ibuffer_index >= (ibuffer_len + pad))
  398                           readNextBuffer();
  399                       in_end = ibuffer_len + pad;
  400                   }
  401   
  402                   if (ibuffer_len < 0) {
  403                       in_end = pad2;
  404                       if (ibuffer_index >= in_end)
  405                           break;
  406                   }
  407   
  408                   if (ibuffer_index < 0)
  409                       break;
  410                   int preDestPos = destPos;
  411                   for (int c = 0; c < nrofchannels; c++) {
  412                       ix[0] = ibuffer_index;
  413                       ox[0] = destPos;
  414                       float[] buff = ibuffer[c];
  415                       resampler.interpolate(buff, ix, in_end, pitch, 0,
  416                               cbuffer[c], ox, len / nrofchannels);
  417                   }
  418                   ibuffer_index = ix[0];
  419                   destPos = ox[0];
  420                   remain -= destPos - preDestPos;
  421               }
  422               for (int c = 0; c < nrofchannels; c++) {
  423                   int ix = 0;
  424                   float[] buff = cbuffer[c];
  425                   for (int i = c + off; i < offlen; i += nrofchannels) {
  426                       b[i] = buff[ix++];
  427                   }
  428               }
  429               return len - remain * nrofchannels;
  430           }
  431   
  432           public void reset() throws IOException {
  433               ais.reset();
  434               if (mark_ibuffer == null)
  435                   return;
  436               ibuffer_index = mark_ibuffer_index;
  437               ibuffer_len = mark_ibuffer_len;
  438               for (int c = 0; c < ibuffer.length; c++) {
  439                   float[] from = mark_ibuffer[c];
  440                   float[] to = ibuffer[c];
  441                   for (int i = 0; i < to.length; i++) {
  442                       to[i] = from[i];
  443                   }
  444               }
  445   
  446           }
  447   
  448           public long skip(long len) throws IOException {
  449               if (len < 0)
  450                   return 0;
  451               if (skipbuffer == null)
  452                   skipbuffer = new float[1024 * targetFormat.getFrameSize()];
  453               float[] l_skipbuffer = skipbuffer;
  454               long remain = len;
  455               while (remain > 0) {
  456                   int ret = read(l_skipbuffer, 0, (int) Math.min(remain,
  457                           skipbuffer.length));
  458                   if (ret < 0) {
  459                       if (remain == len)
  460                           return ret;
  461                       break;
  462                   }
  463                   remain -= ret;
  464               }
  465               return len - remain;
  466   
  467           }
  468   
  469       }
  470   
  471       private Encoding[] formats = { Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED,
  472               Encoding.PCM_FLOAT };
  473   
  474       public AudioInputStream getAudioInputStream(Encoding targetEncoding,
  475               AudioInputStream sourceStream) {
  476           if (sourceStream.getFormat().getEncoding().equals(targetEncoding))
  477               return sourceStream;
  478           AudioFormat format = sourceStream.getFormat();
  479           int channels = format.getChannels();
  480           Encoding encoding = targetEncoding;
  481           float samplerate = format.getSampleRate();
  482           int bits = format.getSampleSizeInBits();
  483           boolean bigendian = format.isBigEndian();
  484           if (targetEncoding.equals(Encoding.PCM_FLOAT))
  485               bits = 32;
  486           AudioFormat targetFormat = new AudioFormat(encoding, samplerate, bits,
  487                   channels, channels * bits / 8, samplerate, bigendian);
  488           return getAudioInputStream(targetFormat, sourceStream);
  489       }
  490   
  491       public AudioInputStream getAudioInputStream(AudioFormat targetFormat,
  492               AudioInputStream sourceStream) {
  493           if (!isConversionSupported(targetFormat, sourceStream.getFormat()))
  494               throw new IllegalArgumentException("Unsupported conversion: "
  495                       + sourceStream.getFormat().toString() + " to "
  496                       + targetFormat.toString());
  497           return getAudioInputStream(targetFormat, AudioFloatInputStream
  498                   .getInputStream(sourceStream));
  499       }
  500   
  501       public AudioInputStream getAudioInputStream(AudioFormat targetFormat,
  502               AudioFloatInputStream sourceStream) {
  503   
  504           if (!isConversionSupported(targetFormat, sourceStream.getFormat()))
  505               throw new IllegalArgumentException("Unsupported conversion: "
  506                       + sourceStream.getFormat().toString() + " to "
  507                       + targetFormat.toString());
  508           if (targetFormat.getChannels() != sourceStream.getFormat()
  509                   .getChannels())
  510               sourceStream = new AudioFloatInputStreamChannelMixer(sourceStream,
  511                       targetFormat.getChannels());
  512           if (Math.abs(targetFormat.getSampleRate()
  513                   - sourceStream.getFormat().getSampleRate()) > 0.000001)
  514               sourceStream = new AudioFloatInputStreamResampler(sourceStream,
  515                       targetFormat);
  516           return new AudioInputStream(new AudioFloatFormatConverterInputStream(
  517                   targetFormat, sourceStream), targetFormat, sourceStream
  518                   .getFrameLength());
  519       }
  520   
  521       public Encoding[] getSourceEncodings() {
  522           return new Encoding[] { Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED,
  523                   Encoding.PCM_FLOAT };
  524       }
  525   
  526       public Encoding[] getTargetEncodings() {
  527           return new Encoding[] { Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED,
  528                   Encoding.PCM_FLOAT };
  529       }
  530   
  531       public Encoding[] getTargetEncodings(AudioFormat sourceFormat) {
  532           if (AudioFloatConverter.getConverter(sourceFormat) == null)
  533               return new Encoding[0];
  534           return new Encoding[] { Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED,
  535                   Encoding.PCM_FLOAT };
  536       }
  537   
  538       public AudioFormat[] getTargetFormats(Encoding targetEncoding,
  539               AudioFormat sourceFormat) {
  540           if (AudioFloatConverter.getConverter(sourceFormat) == null)
  541               return new AudioFormat[0];
  542           int channels = sourceFormat.getChannels();
  543   
  544           ArrayList<AudioFormat> formats = new ArrayList<AudioFormat>();
  545   
  546           if (targetEncoding.equals(Encoding.PCM_SIGNED))
  547               formats.add(new AudioFormat(Encoding.PCM_SIGNED,
  548                       AudioSystem.NOT_SPECIFIED, 8, channels, channels,
  549                       AudioSystem.NOT_SPECIFIED, false));
  550           if (targetEncoding.equals(Encoding.PCM_UNSIGNED))
  551               formats.add(new AudioFormat(Encoding.PCM_UNSIGNED,
  552                       AudioSystem.NOT_SPECIFIED, 8, channels, channels,
  553                       AudioSystem.NOT_SPECIFIED, false));
  554   
  555           for (int bits = 16; bits < 32; bits += 8) {
  556               if (targetEncoding.equals(Encoding.PCM_SIGNED)) {
  557                   formats.add(new AudioFormat(Encoding.PCM_SIGNED,
  558                           AudioSystem.NOT_SPECIFIED, bits, channels, channels
  559                                   * bits / 8, AudioSystem.NOT_SPECIFIED, false));
  560                   formats.add(new AudioFormat(Encoding.PCM_SIGNED,
  561                           AudioSystem.NOT_SPECIFIED, bits, channels, channels
  562                                   * bits / 8, AudioSystem.NOT_SPECIFIED, true));
  563               }
  564               if (targetEncoding.equals(Encoding.PCM_UNSIGNED)) {
  565                   formats.add(new AudioFormat(Encoding.PCM_UNSIGNED,
  566                           AudioSystem.NOT_SPECIFIED, bits, channels, channels
  567                                   * bits / 8, AudioSystem.NOT_SPECIFIED, true));
  568                   formats.add(new AudioFormat(Encoding.PCM_UNSIGNED,
  569                           AudioSystem.NOT_SPECIFIED, bits, channels, channels
  570                                   * bits / 8, AudioSystem.NOT_SPECIFIED, false));
  571               }
  572           }
  573   
  574           if (targetEncoding.equals(Encoding.PCM_FLOAT)) {
  575               formats.add(new AudioFormat(Encoding.PCM_FLOAT,
  576                       AudioSystem.NOT_SPECIFIED, 32, channels, channels * 4,
  577                       AudioSystem.NOT_SPECIFIED, false));
  578               formats.add(new AudioFormat(Encoding.PCM_FLOAT,
  579                       AudioSystem.NOT_SPECIFIED, 32, channels, channels * 4,
  580                       AudioSystem.NOT_SPECIFIED, true));
  581               formats.add(new AudioFormat(Encoding.PCM_FLOAT,
  582                       AudioSystem.NOT_SPECIFIED, 64, channels, channels * 8,
  583                       AudioSystem.NOT_SPECIFIED, false));
  584               formats.add(new AudioFormat(Encoding.PCM_FLOAT,
  585                       AudioSystem.NOT_SPECIFIED, 64, channels, channels * 8,
  586                       AudioSystem.NOT_SPECIFIED, true));
  587           }
  588   
  589           return formats.toArray(new AudioFormat[formats.size()]);
  590       }
  591   
  592       public boolean isConversionSupported(AudioFormat targetFormat,
  593               AudioFormat sourceFormat) {
  594           if (AudioFloatConverter.getConverter(sourceFormat) == null)
  595               return false;
  596           if (AudioFloatConverter.getConverter(targetFormat) == null)
  597               return false;
  598           if (sourceFormat.getChannels() <= 0)
  599               return false;
  600           if (targetFormat.getChannels() <= 0)
  601               return false;
  602           return true;
  603       }
  604   
  605       public boolean isConversionSupported(Encoding targetEncoding,
  606               AudioFormat sourceFormat) {
  607           if (AudioFloatConverter.getConverter(sourceFormat) == null)
  608               return false;
  609           for (int i = 0; i < formats.length; i++) {
  610               if (targetEncoding.equals(formats[i]))
  611                   return true;
  612           }
  613           return false;
  614       }
  615   
  616   }

Save This Page
Home » openjdk-7 » com.sun.media » sound » [javadoc | source]