1 /*
   2  * Copyright (c) 2009, 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 /* @test
  27  @summary Test rendering when using precise timestamps */
  28 
  29 import java.util.Arrays;
  30 import java.util.Random;
  31 
  32 import javax.sound.midi.MidiChannel;
  33 import javax.sound.midi.Receiver;
  34 import javax.sound.midi.ShortMessage;
  35 import javax.sound.midi.Soundbank;
  36 import javax.sound.sampled.AudioFormat;
  37 import javax.sound.sampled.AudioInputStream;
  38 
  39 import com.sun.media.sound.AudioFloatConverter;
  40 import com.sun.media.sound.AudioSynthesizer;
  41 import com.sun.media.sound.ModelAbstractChannelMixer;
  42 import com.sun.media.sound.ModelChannelMixer;
  43 import com.sun.media.sound.SF2Instrument;
  44 import com.sun.media.sound.SF2InstrumentRegion;
  45 import com.sun.media.sound.SF2Layer;
  46 import com.sun.media.sound.SF2LayerRegion;
  47 import com.sun.media.sound.SF2Sample;
  48 import com.sun.media.sound.SF2Soundbank;
  49 import com.sun.media.sound.SimpleInstrument;
  50 import com.sun.media.sound.SimpleSoundbank;
  51 import com.sun.media.sound.SoftSynthesizer;
  52 
  53 public class TestPreciseTimestampRendering {
  54 
  55     public static AudioFormat format = new AudioFormat(44100, 16, 1, true,
  56             false);
  57 
  58     public static SF2Soundbank createTestSoundbank() {
  59         // Create impulse instrument
  60         // used to measure timing of note-on playback
  61         SF2Soundbank soundbank = new SF2Soundbank();
  62         float[] data = new float[100];
  63         Arrays.fill(data, 0);
  64         data[0] = 1.0f;
  65         byte[] bdata = new byte[data.length * format.getFrameSize()];
  66         AudioFloatConverter.getConverter(format).toByteArray(data, bdata);
  67 
  68         SF2Sample sample = new SF2Sample(soundbank);
  69         sample.setName("Test Sample");
  70         sample.setData(bdata);
  71         sample.setSampleRate((long) format.getSampleRate());
  72         sample.setOriginalPitch(69);
  73         soundbank.addResource(sample);
  74 
  75         SF2Layer layer = new SF2Layer(soundbank);
  76         layer.setName("Test Layer");
  77         soundbank.addResource(layer);
  78         SF2LayerRegion region = new SF2LayerRegion();
  79         region.setSample(sample);
  80         layer.getRegions().add(region);
  81 
  82         SF2Instrument ins = new SF2Instrument(soundbank);
  83         ins.setName("Test Instrument");
  84         soundbank.addInstrument(ins);
  85         SF2InstrumentRegion insregion = new SF2InstrumentRegion();
  86         insregion.setLayer(layer);
  87         ins.getRegions().add(insregion);
  88 
  89         return soundbank;
  90     }
  91 
  92     public static Soundbank createTestSoundbankWithChannelMixer() {
  93         SF2Soundbank soundbank = createTestSoundbank();
  94 
  95         SimpleSoundbank simplesoundbank = new SimpleSoundbank();
  96         SimpleInstrument simpleinstrument = new SimpleInstrument() {
  97 
  98             public ModelChannelMixer getChannelMixer(MidiChannel channel,
  99                     AudioFormat format) {
 100                 return new ModelAbstractChannelMixer() {
 101                     boolean active = true;
 102 
 103                     public boolean process(float[][] buffer, int offset, int len) {
 104                         for (int i = 0; i < buffer.length; i++) {
 105                             float[] cbuffer = buffer[i];
 106                             for (int j = 0; j < cbuffer.length; j++) {
 107                                 cbuffer[j] = -cbuffer[j];
 108                             }
 109                         }
 110                         return active;
 111                     }
 112 
 113                     public void stop() {
 114                         active = false;
 115                     }
 116                 };
 117             }
 118 
 119         };
 120         simpleinstrument.add(soundbank.getInstruments()[0]);
 121         simplesoundbank.addInstrument(simpleinstrument);
 122 
 123         return simplesoundbank;
 124     }
 125 
 126     public static void main(String[] args) throws Exception {
 127         test(createTestSoundbank());
 128         test(createTestSoundbankWithChannelMixer());
 129     }
 130 
 131     public static void test(Soundbank soundbank) throws Exception {
 132 
 133         // Create instance of synthesizer using the testing soundbank above
 134         AudioSynthesizer synth = new SoftSynthesizer();
 135         AudioInputStream stream = synth.openStream(format, null);
 136         synth.unloadAllInstruments(synth.getDefaultSoundbank());
 137         synth.loadAllInstruments(soundbank);
 138         Receiver recv = synth.getReceiver();
 139 
 140         // Set volume to max and turn reverb off
 141         ShortMessage reverb_off = new ShortMessage();
 142         reverb_off.setMessage(ShortMessage.CONTROL_CHANGE, 91, 0);
 143         recv.send(reverb_off, -1);
 144         ShortMessage full_volume = new ShortMessage();
 145         full_volume.setMessage(ShortMessage.CONTROL_CHANGE, 7, 127);
 146         recv.send(full_volume, -1);
 147 
 148         Random random = new Random(3485934583945l);
 149 
 150         // Create random timestamps
 151         long[] test_timestamps = new long[30];
 152         for (int i = 1; i < test_timestamps.length; i++) {
 153             test_timestamps[i] = i * 44100
 154                     + (int) (random.nextDouble() * 22050.0);
 155         }
 156 
 157         // Send midi note on message to synthesizer
 158         for (int i = 0; i < test_timestamps.length; i++) {
 159             ShortMessage midi_on = new ShortMessage();
 160             midi_on.setMessage(ShortMessage.NOTE_ON, 69, 127);
 161             recv.send(midi_on,
 162                     (long) ((test_timestamps[i] / 44100.0) * 1000000.0));
 163         }
 164 
 165         // Measure timing from rendered audio
 166         float[] fbuffer = new float[100];
 167         byte[] buffer = new byte[fbuffer.length * format.getFrameSize()];
 168         long firsts = -1;
 169         int counter = 0;
 170         long s = 0;
 171         long max_jitter = 0;
 172         outerloop: for (int k = 0; k < 10000000; k++) {
 173             stream.read(buffer);
 174             AudioFloatConverter.getConverter(format).toFloatArray(buffer,
 175                     fbuffer);
 176             for (int i = 0; i < fbuffer.length; i++) {
 177                 if (fbuffer[i] != 0) {
 178                     if (firsts == -1)
 179                         firsts = s;
 180 
 181                     long measure_time = (s - firsts);
 182                     long predicted_time = test_timestamps[counter];
 183 
 184                     long jitter = Math.abs(measure_time - predicted_time);
 185 
 186                     if (jitter > 10)
 187                         max_jitter = jitter;
 188 
 189                     counter++;
 190                     if (counter == test_timestamps.length)
 191                         break outerloop;
 192                 }
 193                 s++;
 194             }
 195         }
 196         synth.close();
 197 
 198         if (counter == 0)
 199             throw new Exception("Nothing was measured!");
 200 
 201         if (max_jitter != 0) {
 202             throw new Exception("Jitter has occurred! "
 203                     + "(max jitter = " + max_jitter + ")");
 204         }
 205 
 206     }
 207 
 208 }