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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /* @test
  25  @summary Test rendering when using precise timestamps */
  26 
  27 import java.util.Arrays;
  28 import java.util.Random;
  29 
  30 import javax.sound.midi.MidiChannel;
  31 import javax.sound.midi.Receiver;
  32 import javax.sound.midi.ShortMessage;
  33 import javax.sound.midi.Soundbank;
  34 import javax.sound.sampled.AudioFormat;
  35 import javax.sound.sampled.AudioInputStream;
  36 
  37 import com.sun.media.sound.AudioFloatConverter;
  38 import com.sun.media.sound.AudioSynthesizer;
  39 import com.sun.media.sound.ModelAbstractChannelMixer;
  40 import com.sun.media.sound.ModelChannelMixer;
  41 import com.sun.media.sound.SF2Instrument;
  42 import com.sun.media.sound.SF2InstrumentRegion;
  43 import com.sun.media.sound.SF2Layer;
  44 import com.sun.media.sound.SF2LayerRegion;
  45 import com.sun.media.sound.SF2Sample;
  46 import com.sun.media.sound.SF2Soundbank;
  47 import com.sun.media.sound.SimpleInstrument;
  48 import com.sun.media.sound.SimpleSoundbank;
  49 import com.sun.media.sound.SoftSynthesizer;
  50 
  51 public class TestPreciseTimestampRendering {
  52 
  53     public static AudioFormat format = new AudioFormat(44100, 16, 1, true,
  54             false);
  55 
  56     public static SF2Soundbank createTestSoundbank() {
  57         // Create impulse instrument
  58         // used to measure timing of note-on playback
  59         SF2Soundbank soundbank = new SF2Soundbank();
  60         float[] data = new float[100];
  61         Arrays.fill(data, 0);
  62         data[0] = 1.0f;
  63         byte[] bdata = new byte[data.length * format.getFrameSize()];
  64         AudioFloatConverter.getConverter(format).toByteArray(data, bdata);
  65 
  66         SF2Sample sample = new SF2Sample(soundbank);
  67         sample.setName("Test Sample");
  68         sample.setData(bdata);
  69         sample.setSampleRate((long) format.getSampleRate());
  70         sample.setOriginalPitch(69);
  71         soundbank.addResource(sample);
  72 
  73         SF2Layer layer = new SF2Layer(soundbank);
  74         layer.setName("Test Layer");
  75         soundbank.addResource(layer);
  76         SF2LayerRegion region = new SF2LayerRegion();
  77         region.setSample(sample);
  78         layer.getRegions().add(region);
  79 
  80         SF2Instrument ins = new SF2Instrument(soundbank);
  81         ins.setName("Test Instrument");
  82         soundbank.addInstrument(ins);
  83         SF2InstrumentRegion insregion = new SF2InstrumentRegion();
  84         insregion.setLayer(layer);
  85         ins.getRegions().add(insregion);
  86 
  87         return soundbank;
  88     }
  89 
  90     public static Soundbank createTestSoundbankWithChannelMixer() {
  91         SF2Soundbank soundbank = createTestSoundbank();
  92 
  93         SimpleSoundbank simplesoundbank = new SimpleSoundbank();
  94         SimpleInstrument simpleinstrument = new SimpleInstrument() {
  95 
  96             public ModelChannelMixer getChannelMixer(MidiChannel channel,
  97                     AudioFormat format) {
  98                 return new ModelAbstractChannelMixer() {
  99                     boolean active = true;
 100 
 101                     public boolean process(float[][] buffer, int offset, int len) {
 102                         for (int i = 0; i < buffer.length; i++) {
 103                             float[] cbuffer = buffer[i];
 104                             for (int j = 0; j < cbuffer.length; j++) {
 105                                 cbuffer[j] = -cbuffer[j];
 106                             }
 107                         }
 108                         return active;
 109                     }
 110 
 111                     public void stop() {
 112                         active = false;
 113                     }
 114                 };
 115             }
 116 
 117         };
 118         simpleinstrument.add(soundbank.getInstruments()[0]);
 119         simplesoundbank.addInstrument(simpleinstrument);
 120 
 121         return simplesoundbank;
 122     }
 123 
 124     public static void main(String[] args) throws Exception {
 125         test(createTestSoundbank());
 126         test(createTestSoundbankWithChannelMixer());
 127     }
 128 
 129     public static void test(Soundbank soundbank) throws Exception {
 130 
 131         // Create instance of synthesizer using the testing soundbank above
 132         AudioSynthesizer synth = new SoftSynthesizer();
 133         AudioInputStream stream = synth.openStream(format, null);
 134         synth.unloadAllInstruments(synth.getDefaultSoundbank());
 135         synth.loadAllInstruments(soundbank);
 136         Receiver recv = synth.getReceiver();
 137 
 138         // Set volume to max and turn reverb off
 139         ShortMessage reverb_off = new ShortMessage();
 140         reverb_off.setMessage(ShortMessage.CONTROL_CHANGE, 91, 0);
 141         recv.send(reverb_off, -1);
 142         ShortMessage full_volume = new ShortMessage();
 143         full_volume.setMessage(ShortMessage.CONTROL_CHANGE, 7, 127);
 144         recv.send(full_volume, -1);
 145 
 146         Random random = new Random(3485934583945l);
 147 
 148         // Create random timestamps
 149         long[] test_timestamps = new long[30];
 150         for (int i = 1; i < test_timestamps.length; i++) {
 151             test_timestamps[i] = i * 44100
 152                     + (int) (random.nextDouble() * 22050.0);
 153         }
 154 
 155         // Send midi note on message to synthesizer
 156         for (int i = 0; i < test_timestamps.length; i++) {
 157             ShortMessage midi_on = new ShortMessage();
 158             midi_on.setMessage(ShortMessage.NOTE_ON, 69, 127);
 159             recv.send(midi_on,
 160                     (long) ((test_timestamps[i] / 44100.0) * 1000000.0));
 161         }
 162 
 163         // Measure timing from rendered audio
 164         float[] fbuffer = new float[100];
 165         byte[] buffer = new byte[fbuffer.length * format.getFrameSize()];
 166         long firsts = -1;
 167         int counter = 0;
 168         long s = 0;
 169         long max_jitter = 0;
 170         outerloop: for (int k = 0; k < 10000000; k++) {
 171             stream.read(buffer);
 172             AudioFloatConverter.getConverter(format).toFloatArray(buffer,
 173                     fbuffer);
 174             for (int i = 0; i < fbuffer.length; i++) {
 175                 if (fbuffer[i] != 0) {
 176                     if (firsts == -1)
 177                         firsts = s;
 178 
 179                     long measure_time = (s - firsts);
 180                     long predicted_time = test_timestamps[counter];
 181 
 182                     long jitter = Math.abs(measure_time - predicted_time);
 183 
 184                     if (jitter > 10)
 185                         max_jitter = jitter;
 186 
 187                     counter++;
 188                     if (counter == test_timestamps.length)
 189                         break outerloop;
 190                 }
 191                 s++;
 192             }
 193         }
 194         synth.close();
 195 
 196         if (counter == 0)
 197             throw new Exception("Nothing was measured!");
 198 
 199         if (max_jitter != 0) {
 200             throw new Exception("Jitter has occurred! "
 201                     + "(max jitter = " + max_jitter + ")");
 202         }
 203 
 204     }
 205 
 206 }