From dca3f935d4605907540eeb1bb87e2440fab4d000 Mon Sep 17 00:00:00 2001 From: Karl Helgason Date: Fri, 27 Nov 2009 17:13:02 +0300 Subject: [PATCH] 6833357: Improve time-stamp support in Gervill to reduce jitter Reviewed-by: amenkov --- .../sun/media/sound/MidiDeviceReceiver.java | 41 ++++ .../com/sun/media/sound/SoftAudioBuffer.java | 24 ++ .../com/sun/media/sound/SoftChannel.java | 21 +- .../com/sun/media/sound/SoftLimiter.java | 2 +- .../com/sun/media/sound/SoftMainMixer.java | 218 ++++++++++++++---- .../com/sun/media/sound/SoftReceiver.java | 9 +- .../com/sun/media/sound/SoftSynthesizer.java | 5 + .../com/sun/media/sound/SoftVoice.java | 95 +++++--- .../Gervill/SoftReceiver/GetMidiDevice.java | 48 ++++ .../TestPreciseTimestampRendering.java | 208 +++++++++++++++++ 10 files changed, 585 insertions(+), 86 deletions(-) create mode 100644 jdk/src/share/classes/com/sun/media/sound/MidiDeviceReceiver.java create mode 100644 jdk/test/javax/sound/midi/Gervill/SoftReceiver/GetMidiDevice.java create mode 100644 jdk/test/javax/sound/midi/Gervill/SoftSynthesizer/TestPreciseTimestampRendering.java diff --git a/jdk/src/share/classes/com/sun/media/sound/MidiDeviceReceiver.java b/jdk/src/share/classes/com/sun/media/sound/MidiDeviceReceiver.java new file mode 100644 index 00000000000..60bca739318 --- /dev/null +++ b/jdk/src/share/classes/com/sun/media/sound/MidiDeviceReceiver.java @@ -0,0 +1,41 @@ +/* + * Copyright 2009 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ +package com.sun.media.sound; + +import javax.sound.midi.MidiDevice; +import javax.sound.midi.Receiver; + +/** + * A Receiver with reference to it's MidiDevice object. + * + * @author Karl Helgason + */ +public interface MidiDeviceReceiver extends Receiver { + + /** Obtains the MidiDevice object associated with this Receiver. + */ + public MidiDevice getMidiDevice(); + +} diff --git a/jdk/src/share/classes/com/sun/media/sound/SoftAudioBuffer.java b/jdk/src/share/classes/com/sun/media/sound/SoftAudioBuffer.java index ccc94899f61..fc3bb4c7322 100644 --- a/jdk/src/share/classes/com/sun/media/sound/SoftAudioBuffer.java +++ b/jdk/src/share/classes/com/sun/media/sound/SoftAudioBuffer.java @@ -48,6 +48,30 @@ public class SoftAudioBuffer { converter = AudioFloatConverter.getConverter(format); } + public void swap(SoftAudioBuffer swap) + { + int bak_size = size; + float[] bak_buffer = buffer; + boolean bak_empty = empty; + AudioFormat bak_format = format; + AudioFloatConverter bak_converter = converter; + byte[] bak_converter_buffer = converter_buffer; + + size = swap.size; + buffer = swap.buffer; + empty = swap.empty; + format = swap.format; + converter = swap.converter; + converter_buffer = swap.converter_buffer; + + swap.size = bak_size; + swap.buffer = bak_buffer; + swap.empty = bak_empty; + swap.format = bak_format; + swap.converter = bak_converter; + swap.converter_buffer = bak_converter_buffer; + } + public AudioFormat getFormat() { return format; } diff --git a/jdk/src/share/classes/com/sun/media/sound/SoftChannel.java b/jdk/src/share/classes/com/sun/media/sound/SoftChannel.java index 96a575f2c8e..f30c67f10c4 100644 --- a/jdk/src/share/classes/com/sun/media/sound/SoftChannel.java +++ b/jdk/src/share/classes/com/sun/media/sound/SoftChannel.java @@ -328,7 +328,7 @@ public class SoftChannel implements MidiChannel, ModelDirectedPlayer { } protected void initVoice(SoftVoice voice, SoftPerformer p, int voiceID, - int noteNumber, int velocity, ModelConnectionBlock[] connectionBlocks, + int noteNumber, int velocity, int delay, ModelConnectionBlock[] connectionBlocks, ModelChannelMixer channelmixer, boolean releaseTriggered) { if (voice.active) { // Voice is active , we must steal the voice @@ -363,7 +363,7 @@ public class SoftChannel implements MidiChannel, ModelDirectedPlayer { voice.objects.put("midi_cc", co_midi_cc); voice.objects.put("midi_rpn", co_midi_rpn); voice.objects.put("midi_nrpn", co_midi_nrpn); - voice.noteOn(noteNumber, velocity); + voice.noteOn(noteNumber, velocity, delay); voice.setMute(mute); voice.setSoloMute(solomute); if (releaseTriggered) @@ -399,14 +399,21 @@ public class SoftChannel implements MidiChannel, ModelDirectedPlayer { } public void noteOn(int noteNumber, int velocity) { + noteOn(noteNumber, velocity, 0); + } + + /* A special noteOn with delay parameter, which is used to + * start note within control buffers. + */ + protected void noteOn(int noteNumber, int velocity, int delay) { noteNumber = restrict7Bit(noteNumber); velocity = restrict7Bit(velocity); - noteOn_internal(noteNumber, velocity); + noteOn_internal(noteNumber, velocity, delay); if (current_mixer != null) current_mixer.noteOn(noteNumber, velocity); } - private void noteOn_internal(int noteNumber, int velocity) { + private void noteOn_internal(int noteNumber, int velocity, int delay) { if (velocity == 0) { noteOff_internal(noteNumber, 64); @@ -490,6 +497,7 @@ public class SoftChannel implements MidiChannel, ModelDirectedPlayer { int tunedKey = (int)(Math.round(tuning.getTuning()[noteNumber]/100.0)); play_noteNumber = noteNumber; play_velocity = velocity; + play_delay = delay; play_releasetriggered = false; lastVelocity[noteNumber] = velocity; current_director.noteOn(tunedKey, velocity); @@ -594,6 +602,7 @@ public class SoftChannel implements MidiChannel, ModelDirectedPlayer { play_noteNumber = noteNumber; play_velocity = lastVelocity[noteNumber]; play_releasetriggered = true; + play_delay = 0; current_director.noteOff(tunedKey, velocity); } @@ -604,12 +613,14 @@ public class SoftChannel implements MidiChannel, ModelDirectedPlayer { private int voiceNo = 0; private int play_noteNumber = 0; private int play_velocity = 0; + private int play_delay = 0; private boolean play_releasetriggered = false; public void play(int performerIndex, ModelConnectionBlock[] connectionBlocks) { int noteNumber = play_noteNumber; int velocity = play_velocity; + int delay = play_delay; boolean releasetriggered = play_releasetriggered; SoftPerformer p = current_instrument.getPerformers()[performerIndex]; @@ -633,7 +644,7 @@ public class SoftChannel implements MidiChannel, ModelDirectedPlayer { if (voiceNo == -1) return; - initVoice(voices[voiceNo], p, prevVoiceID, noteNumber, velocity, + initVoice(voices[voiceNo], p, prevVoiceID, noteNumber, velocity, delay, connectionBlocks, current_mixer, releasetriggered); } diff --git a/jdk/src/share/classes/com/sun/media/sound/SoftLimiter.java b/jdk/src/share/classes/com/sun/media/sound/SoftLimiter.java index 7ba0ac66002..cad04ecfd44 100644 --- a/jdk/src/share/classes/com/sun/media/sound/SoftLimiter.java +++ b/jdk/src/share/classes/com/sun/media/sound/SoftLimiter.java @@ -79,7 +79,7 @@ public class SoftLimiter implements SoftAudioProcessor { if (silentcounter > 60) { if (!mix) { bufferLout.clear(); - bufferRout.clear(); + if (bufferRout != null) bufferRout.clear(); } return; } diff --git a/jdk/src/share/classes/com/sun/media/sound/SoftMainMixer.java b/jdk/src/share/classes/com/sun/media/sound/SoftMainMixer.java index b62d1d480af..2d627674fa8 100644 --- a/jdk/src/share/classes/com/sun/media/sound/SoftMainMixer.java +++ b/jdk/src/share/classes/com/sun/media/sound/SoftMainMixer.java @@ -26,7 +26,6 @@ package com.sun.media.sound; import java.io.IOException; import java.io.InputStream; -import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.Set; @@ -46,28 +45,37 @@ import javax.sound.sampled.AudioSystem; */ public class SoftMainMixer { + // A private class thats contains a ModelChannelMixer and it's private buffers. + // This becomes necessary when we want to have separate delay buffers for each channel mixer. + private class SoftChannelMixerContainer + { + ModelChannelMixer mixer; + SoftAudioBuffer[] buffers; + } + public final static int CHANNEL_LEFT = 0; public final static int CHANNEL_RIGHT = 1; public final static int CHANNEL_MONO = 2; - public final static int CHANNEL_EFFECT1 = 3; - public final static int CHANNEL_EFFECT2 = 4; - public final static int CHANNEL_EFFECT3 = 5; - public final static int CHANNEL_EFFECT4 = 6; + public final static int CHANNEL_DELAY_LEFT = 3; + public final static int CHANNEL_DELAY_RIGHT = 4; + public final static int CHANNEL_DELAY_MONO = 5; + public final static int CHANNEL_EFFECT1 = 6; + public final static int CHANNEL_EFFECT2 = 7; + public final static int CHANNEL_DELAY_EFFECT1 = 8; + public final static int CHANNEL_DELAY_EFFECT2 = 9; public final static int CHANNEL_LEFT_DRY = 10; public final static int CHANNEL_RIGHT_DRY = 11; public final static int CHANNEL_SCRATCH1 = 12; public final static int CHANNEL_SCRATCH2 = 13; - public final static int CHANNEL_CHANNELMIXER_LEFT = 14; - public final static int CHANNEL_CHANNELMIXER_RIGHT = 15; - public final static int CHANNEL_CHANNELMIXER_MONO = 16; protected boolean active_sensing_on = false; private long msec_last_activity = -1; private boolean pusher_silent = false; private int pusher_silent_count = 0; - private long msec_pos = 0; + private long sample_pos = 0; protected boolean readfully = true; private Object control_mutex; private SoftSynthesizer synth; + private float samplerate = 44100; private int nrofchannels = 2; private SoftVoice[] voicestatus = null; private SoftAudioBuffer[] buffers; @@ -75,7 +83,10 @@ public class SoftMainMixer { private SoftAudioProcessor chorus; private SoftAudioProcessor agc; private long msec_buffer_len = 0; + private int buffer_len = 0; protected TreeMap midimessages = new TreeMap(); + private int delay_midievent = 0; + private int max_delay_midievent = 0; double last_volume_left = 1.0; double last_volume_right = 1.0; private double[] co_master_balance = new double[1]; @@ -83,9 +94,9 @@ public class SoftMainMixer { private double[] co_master_coarse_tuning = new double[1]; private double[] co_master_fine_tuning = new double[1]; private AudioInputStream ais; - private Set registeredMixers = null; + private Set registeredMixers = null; private Set stoppedMixers = null; - private ModelChannelMixer[] cur_registeredMixers = null; + private SoftChannelMixerContainer[] cur_registeredMixers = null; protected SoftControl co_master = new SoftControl() { double[] balance = co_master_balance; @@ -413,26 +424,68 @@ public class SoftMainMixer { Iterator> iter = midimessages.entrySet().iterator(); while (iter.hasNext()) { Entry entry = iter.next(); - if (entry.getKey() > (timeStamp + 100)) + if (entry.getKey() >= (timeStamp + msec_buffer_len)) return; + long msec_delay = entry.getKey() - timeStamp; + delay_midievent = (int)(msec_delay * (samplerate / 1000000.0) + 0.5); + if(delay_midievent > max_delay_midievent) + delay_midievent = max_delay_midievent; + if(delay_midievent < 0) + delay_midievent = 0; processMessage(entry.getValue()); iter.remove(); } + delay_midievent = 0; } protected void processAudioBuffers() { + + if(synth.weakstream != null && synth.weakstream.silent_samples != 0) + { + sample_pos += synth.weakstream.silent_samples; + synth.weakstream.silent_samples = 0; + } + for (int i = 0; i < buffers.length; i++) { - buffers[i].clear(); + if(i != CHANNEL_DELAY_LEFT && + i != CHANNEL_DELAY_RIGHT && + i != CHANNEL_DELAY_MONO && + i != CHANNEL_DELAY_EFFECT1 && + i != CHANNEL_DELAY_EFFECT2) + buffers[i].clear(); + } + + if(!buffers[CHANNEL_DELAY_LEFT].isSilent()) + { + buffers[CHANNEL_LEFT].swap(buffers[CHANNEL_DELAY_LEFT]); + } + if(!buffers[CHANNEL_DELAY_RIGHT].isSilent()) + { + buffers[CHANNEL_RIGHT].swap(buffers[CHANNEL_DELAY_RIGHT]); + } + if(!buffers[CHANNEL_DELAY_MONO].isSilent()) + { + buffers[CHANNEL_MONO].swap(buffers[CHANNEL_DELAY_MONO]); + } + if(!buffers[CHANNEL_DELAY_EFFECT1].isSilent()) + { + buffers[CHANNEL_EFFECT1].swap(buffers[CHANNEL_DELAY_EFFECT1]); + } + if(!buffers[CHANNEL_DELAY_EFFECT2].isSilent()) + { + buffers[CHANNEL_EFFECT2].swap(buffers[CHANNEL_DELAY_EFFECT2]); } double volume_left; double volume_right; - ModelChannelMixer[] act_registeredMixers; + SoftChannelMixerContainer[] act_registeredMixers; // perform control logic synchronized (control_mutex) { + long msec_pos = (long)(sample_pos * (1000000.0 / samplerate)); + processMessages(msec_pos); if (active_sensing_on) { @@ -450,7 +503,7 @@ public class SoftMainMixer { for (int i = 0; i < voicestatus.length; i++) if (voicestatus[i].active) voicestatus[i].processControlLogic(); - msec_pos += msec_buffer_len; + sample_pos += buffer_len; double volume = co_master_volume[0]; volume_left = volume; @@ -469,7 +522,7 @@ public class SoftMainMixer { if (cur_registeredMixers == null) { if (registeredMixers != null) { cur_registeredMixers = - new ModelChannelMixer[registeredMixers.size()]; + new SoftChannelMixerContainer[registeredMixers.size()]; registeredMixers.toArray(cur_registeredMixers); } } @@ -483,44 +536,61 @@ public class SoftMainMixer { if (act_registeredMixers != null) { - // Reroute default left,right output - // to channelmixer left,right input/output + // Make backup of left,right,mono channels SoftAudioBuffer leftbak = buffers[CHANNEL_LEFT]; SoftAudioBuffer rightbak = buffers[CHANNEL_RIGHT]; SoftAudioBuffer monobak = buffers[CHANNEL_MONO]; - buffers[CHANNEL_LEFT] = buffers[CHANNEL_CHANNELMIXER_LEFT]; - buffers[CHANNEL_RIGHT] = buffers[CHANNEL_CHANNELMIXER_RIGHT]; - buffers[CHANNEL_MONO] = buffers[CHANNEL_CHANNELMIXER_MONO]; + SoftAudioBuffer delayleftbak = buffers[CHANNEL_DELAY_LEFT]; + SoftAudioBuffer delayrightbak = buffers[CHANNEL_DELAY_RIGHT]; + SoftAudioBuffer delaymonobak = buffers[CHANNEL_DELAY_MONO]; int bufferlen = buffers[CHANNEL_LEFT].getSize(); float[][] cbuffer = new float[nrofchannels][]; - cbuffer[0] = buffers[CHANNEL_LEFT].array(); - if (nrofchannels != 1) - cbuffer[1] = buffers[CHANNEL_RIGHT].array(); - float[][] obuffer = new float[nrofchannels][]; obuffer[0] = leftbak.array(); if (nrofchannels != 1) obuffer[1] = rightbak.array(); - for (ModelChannelMixer cmixer : act_registeredMixers) { - for (int i = 0; i < cbuffer.length; i++) - Arrays.fill(cbuffer[i], 0); + for (SoftChannelMixerContainer cmixer : act_registeredMixers) { + + // Reroute default left,right output + // to channelmixer left,right input/output + buffers[CHANNEL_LEFT] = cmixer.buffers[CHANNEL_LEFT]; + buffers[CHANNEL_RIGHT] = cmixer.buffers[CHANNEL_RIGHT]; + buffers[CHANNEL_MONO] = cmixer.buffers[CHANNEL_MONO]; + buffers[CHANNEL_DELAY_LEFT] = cmixer.buffers[CHANNEL_DELAY_LEFT]; + buffers[CHANNEL_DELAY_RIGHT] = cmixer.buffers[CHANNEL_DELAY_RIGHT]; + buffers[CHANNEL_DELAY_MONO] = cmixer.buffers[CHANNEL_DELAY_MONO]; + + buffers[CHANNEL_LEFT].clear(); + buffers[CHANNEL_RIGHT].clear(); buffers[CHANNEL_MONO].clear(); + + if(!buffers[CHANNEL_DELAY_LEFT].isSilent()) + { + buffers[CHANNEL_LEFT].swap(buffers[CHANNEL_DELAY_LEFT]); + } + if(!buffers[CHANNEL_DELAY_RIGHT].isSilent()) + { + buffers[CHANNEL_RIGHT].swap(buffers[CHANNEL_DELAY_RIGHT]); + } + if(!buffers[CHANNEL_DELAY_MONO].isSilent()) + { + buffers[CHANNEL_MONO].swap(buffers[CHANNEL_DELAY_MONO]); + } + + cbuffer[0] = buffers[CHANNEL_LEFT].array(); + if (nrofchannels != 1) + cbuffer[1] = buffers[CHANNEL_RIGHT].array(); + boolean hasactivevoices = false; for (int i = 0; i < voicestatus.length; i++) if (voicestatus[i].active) - if (voicestatus[i].channelmixer == cmixer) { + if (voicestatus[i].channelmixer == cmixer.mixer) { voicestatus[i].processAudioLogic(buffers); hasactivevoices = true; } - if (!cmixer.process(cbuffer, 0, bufferlen)) { - synchronized (control_mutex) { - registeredMixers.remove(cmixer); - cur_registeredMixers = null; - } - } if(!buffers[CHANNEL_MONO].isSilent()) { @@ -542,6 +612,13 @@ public class SoftMainMixer { } } + if (!cmixer.mixer.process(cbuffer, 0, bufferlen)) { + synchronized (control_mutex) { + registeredMixers.remove(cmixer); + cur_registeredMixers = null; + } + } + for (int i = 0; i < cbuffer.length; i++) { float[] cbuff = cbuffer[i]; float[] obuff = obuffer[i]; @@ -554,7 +631,7 @@ public class SoftMainMixer { if (stoppedMixers != null) { if (stoppedMixers.contains(cmixer)) { stoppedMixers.remove(cmixer); - cmixer.stop(); + cmixer.mixer.stop(); } } } @@ -565,6 +642,9 @@ public class SoftMainMixer { buffers[CHANNEL_LEFT] = leftbak; buffers[CHANNEL_RIGHT] = rightbak; buffers[CHANNEL_MONO] = monobak; + buffers[CHANNEL_DELAY_LEFT] = delayleftbak; + buffers[CHANNEL_DELAY_RIGHT] = delayrightbak; + buffers[CHANNEL_DELAY_MONO] = delaymonobak; } @@ -650,14 +730,23 @@ public class SoftMainMixer { if(buffers[CHANNEL_LEFT].isSilent() && buffers[CHANNEL_RIGHT].isSilent()) { - pusher_silent_count++; - if(pusher_silent_count > 5) + + int midimessages_size; + synchronized (control_mutex) { + midimessages_size = midimessages.size(); + } + + if(midimessages_size == 0) { - pusher_silent_count = 0; - synchronized (control_mutex) { - pusher_silent = true; - if(synth.weakstream != null) - synth.weakstream.setInputStream(null); + pusher_silent_count++; + if(pusher_silent_count > 5) + { + pusher_silent_count = 0; + synchronized (control_mutex) { + pusher_silent = true; + if(synth.weakstream != null) + synth.weakstream.setInputStream(null); + } } } } @@ -672,13 +761,18 @@ public class SoftMainMixer { // Must only we called within control_mutex synchronization public void activity() { - msec_last_activity = msec_pos; + long silent_samples = 0; if(pusher_silent) { pusher_silent = false; if(synth.weakstream != null) + { synth.weakstream.setInputStream(ais); + silent_samples = synth.weakstream.silent_samples; + } } + msec_last_activity = (long)((sample_pos + silent_samples) + * (1000000.0 / samplerate)); } public void stopMixer(ModelChannelMixer mixer) { @@ -689,15 +783,22 @@ public class SoftMainMixer { public void registerMixer(ModelChannelMixer mixer) { if (registeredMixers == null) - registeredMixers = new HashSet(); - registeredMixers.add(mixer); + registeredMixers = new HashSet(); + SoftChannelMixerContainer mixercontainer = new SoftChannelMixerContainer(); + mixercontainer.buffers = new SoftAudioBuffer[6]; + for (int i = 0; i < mixercontainer.buffers.length; i++) { + mixercontainer.buffers[i] = + new SoftAudioBuffer(buffer_len, synth.getFormat()); + } + mixercontainer.mixer = mixer; + registeredMixers.add(mixercontainer); cur_registeredMixers = null; } public SoftMainMixer(SoftSynthesizer synth) { this.synth = synth; - msec_pos = 0; + sample_pos = 0; co_master_balance[0] = 0.5; co_master_volume[0] = 1; @@ -705,14 +806,18 @@ public class SoftMainMixer { co_master_fine_tuning[0] = 0.5; msec_buffer_len = (long) (1000000.0 / synth.getControlRate()); - + samplerate = synth.getFormat().getSampleRate(); nrofchannels = synth.getFormat().getChannels(); int buffersize = (int) (synth.getFormat().getSampleRate() / synth.getControlRate()); + buffer_len = buffersize; + + max_delay_midievent = buffersize; + control_mutex = synth.control_mutex; - buffers = new SoftAudioBuffer[17]; + buffers = new SoftAudioBuffer[14]; for (int i = 0; i < buffers.length; i++) { buffers[i] = new SoftAudioBuffer(buffersize, synth.getFormat()); } @@ -994,7 +1099,10 @@ public class SoftMainMixer { switch (cmd) { case ShortMessage.NOTE_ON: - softchannel.noteOn(data1, data2); + if(delay_midievent != 0) + softchannel.noteOn(data1, data2, delay_midievent); + else + softchannel.noteOn(data1, data2); break; case ShortMessage.NOTE_OFF: softchannel.noteOff(data1, data2); @@ -1021,7 +1129,15 @@ public class SoftMainMixer { } public long getMicrosecondPosition() { - return msec_pos; + if(pusher_silent) + { + if(synth.weakstream != null) + { + return (long)((sample_pos + synth.weakstream.silent_samples) + * (1000000.0 / samplerate)); + } + } + return (long)(sample_pos * (1000000.0 / samplerate)); } public void close() { diff --git a/jdk/src/share/classes/com/sun/media/sound/SoftReceiver.java b/jdk/src/share/classes/com/sun/media/sound/SoftReceiver.java index e7f1edcfae6..fa533836c7b 100644 --- a/jdk/src/share/classes/com/sun/media/sound/SoftReceiver.java +++ b/jdk/src/share/classes/com/sun/media/sound/SoftReceiver.java @@ -26,8 +26,8 @@ package com.sun.media.sound; import java.util.TreeMap; +import javax.sound.midi.MidiDevice; import javax.sound.midi.MidiMessage; -import javax.sound.midi.Receiver; import javax.sound.midi.ShortMessage; /** @@ -35,7 +35,7 @@ import javax.sound.midi.ShortMessage; * * @author Karl Helgason */ -public class SoftReceiver implements Receiver { +public class SoftReceiver implements MidiDeviceReceiver { protected boolean open = true; private Object control_mutex; @@ -51,6 +51,10 @@ public class SoftReceiver implements Receiver { this.midimessages = mainmixer.midimessages; } + public MidiDevice getMidiDevice() { + return synth; + } + public void send(MidiMessage message, long timeStamp) { synchronized (control_mutex) { @@ -60,6 +64,7 @@ public class SoftReceiver implements Receiver { if (timeStamp != -1) { synchronized (control_mutex) { + mainmixer.activity(); while (midimessages.get(timeStamp) != null) timeStamp++; if (message instanceof ShortMessage diff --git a/jdk/src/share/classes/com/sun/media/sound/SoftSynthesizer.java b/jdk/src/share/classes/com/sun/media/sound/SoftSynthesizer.java index ab46b2dc9a9..6f867f7ebab 100644 --- a/jdk/src/share/classes/com/sun/media/sound/SoftSynthesizer.java +++ b/jdk/src/share/classes/com/sun/media/sound/SoftSynthesizer.java @@ -66,6 +66,8 @@ public class SoftSynthesizer implements AudioSynthesizer, public SoftAudioPusher pusher = null; public AudioInputStream jitter_stream = null; public SourceDataLine sourceDataLine = null; + public volatile long silent_samples = 0; + private int framesize = 0; private WeakReference weak_stream_link; private AudioFloatConverter converter; private float[] silentbuffer = null; @@ -101,6 +103,8 @@ public class SoftSynthesizer implements AudioSynthesizer, silentbuffer = new float[flen]; converter.toByteArray(silentbuffer, flen, b, off); + silent_samples += (long)((len / framesize)); + if(pusher != null) if(weak_stream_link.get() == null) { @@ -136,6 +140,7 @@ public class SoftSynthesizer implements AudioSynthesizer, weak_stream_link = new WeakReference(stream); converter = AudioFloatConverter.getConverter(stream.getFormat()); samplesize = stream.getFormat().getFrameSize() / stream.getFormat().getChannels(); + framesize = stream.getFormat().getFrameSize(); } public AudioInputStream getAudioInputStream() diff --git a/jdk/src/share/classes/com/sun/media/sound/SoftVoice.java b/jdk/src/share/classes/com/sun/media/sound/SoftVoice.java index cec2e3047ac..13f9d366947 100644 --- a/jdk/src/share/classes/com/sun/media/sound/SoftVoice.java +++ b/jdk/src/share/classes/com/sun/media/sound/SoftVoice.java @@ -43,6 +43,7 @@ public class SoftVoice extends VoiceStatus { private int noteOn_noteNumber = 0; private int noteOn_velocity = 0; private int noteOff_velocity = 0; + private int delay = 0; protected ModelChannelMixer channelmixer = null; protected double tunedKey = 0; protected SoftTuning tuning = null; @@ -294,7 +295,7 @@ public class SoftVoice extends VoiceStatus { tunedKey = tuning.getTuning(noteNumber) / 100.0; } - protected void noteOn(int noteNumber, int velocity) { + protected void noteOn(int noteNumber, int velocity, int delay) { sustain = false; sostenuto = false; @@ -308,6 +309,7 @@ public class SoftVoice extends VoiceStatus { noteOn_noteNumber = noteNumber; noteOn_velocity = velocity; + this.delay = delay; lastMuteValue = 0; lastSoloMuteValue = 0; @@ -562,7 +564,7 @@ public class SoftVoice extends VoiceStatus { if (stealer_channel != null) { stealer_channel.initVoice(this, stealer_performer, - stealer_voiceID, stealer_noteNumber, stealer_velocity, + stealer_voiceID, stealer_noteNumber, stealer_velocity, 0, stealer_extendedConnectionBlocks, stealer_channelmixer, stealer_releaseTriggered); stealer_releaseTriggered = false; @@ -733,23 +735,55 @@ public class SoftVoice extends VoiceStatus { } protected void mixAudioStream(SoftAudioBuffer in, SoftAudioBuffer out, + SoftAudioBuffer dout, float amp_from, float amp_to) { int bufferlen = in.getSize(); if (amp_from < 0.000000001 && amp_to < 0.000000001) return; - if (amp_from == amp_to) { - float[] fout = out.array(); - float[] fin = in.array(); - for (int i = 0; i < bufferlen; i++) - fout[i] += fin[i] * amp_to; - } else { - float amp = amp_from; - float amp_delta = (amp_to - amp_from) / bufferlen; - float[] fout = out.array(); - float[] fin = in.array(); - for (int i = 0; i < bufferlen; i++) { - amp += amp_delta; - fout[i] += fin[i] * amp; + if(dout != null && delay != 0) + { + if (amp_from == amp_to) { + float[] fout = out.array(); + float[] fin = in.array(); + int j = 0; + for (int i = delay; i < bufferlen; i++) + fout[i] += fin[j++] * amp_to; + fout = dout.array(); + for (int i = 0; i < delay; i++) + fout[i] += fin[j++] * amp_to; + } else { + float amp = amp_from; + float amp_delta = (amp_to - amp_from) / bufferlen; + float[] fout = out.array(); + float[] fin = in.array(); + int j = 0; + for (int i = delay; i < bufferlen; i++) { + amp += amp_delta; + fout[i] += fin[j++] * amp; + } + fout = dout.array(); + for (int i = 0; i < delay; i++) { + amp += amp_delta; + fout[i] += fin[j++] * amp; + } + } + } + else + { + if (amp_from == amp_to) { + float[] fout = out.array(); + float[] fin = in.array(); + for (int i = 0; i < bufferlen; i++) + fout[i] += fin[i] * amp_to; + } else { + float amp = amp_from; + float amp_delta = (amp_to - amp_from) / bufferlen; + float[] fout = out.array(); + float[] fin = in.array(); + for (int i = 0; i < bufferlen; i++) { + amp += amp_delta; + fout[i] += fin[i] * amp; + } } } @@ -785,6 +819,13 @@ public class SoftVoice extends VoiceStatus { SoftAudioBuffer mono = buffer[SoftMainMixer.CHANNEL_MONO]; SoftAudioBuffer eff1 = buffer[SoftMainMixer.CHANNEL_EFFECT1]; SoftAudioBuffer eff2 = buffer[SoftMainMixer.CHANNEL_EFFECT2]; + + SoftAudioBuffer dleft = buffer[SoftMainMixer.CHANNEL_DELAY_LEFT]; + SoftAudioBuffer dright = buffer[SoftMainMixer.CHANNEL_DELAY_RIGHT]; + SoftAudioBuffer dmono = buffer[SoftMainMixer.CHANNEL_DELAY_MONO]; + SoftAudioBuffer deff1 = buffer[SoftMainMixer.CHANNEL_DELAY_EFFECT1]; + SoftAudioBuffer deff2 = buffer[SoftMainMixer.CHANNEL_DELAY_EFFECT2]; + SoftAudioBuffer leftdry = buffer[SoftMainMixer.CHANNEL_LEFT_DRY]; SoftAudioBuffer rightdry = buffer[SoftMainMixer.CHANNEL_RIGHT_DRY]; @@ -799,42 +840,42 @@ public class SoftVoice extends VoiceStatus { if (nrofchannels == 1) { out_mixer_left = (out_mixer_left + out_mixer_right) / 2; - mixAudioStream(leftdry, left, last_out_mixer_left, out_mixer_left); + mixAudioStream(leftdry, left, dleft, last_out_mixer_left, out_mixer_left); if (rightdry != null) - mixAudioStream(rightdry, left, last_out_mixer_left, + mixAudioStream(rightdry, left, dleft, last_out_mixer_left, out_mixer_left); } else { if(rightdry == null && last_out_mixer_left == last_out_mixer_right && out_mixer_left == out_mixer_right) { - mixAudioStream(leftdry, mono, last_out_mixer_left, out_mixer_left); + mixAudioStream(leftdry, mono, dmono, last_out_mixer_left, out_mixer_left); } else { - mixAudioStream(leftdry, left, last_out_mixer_left, out_mixer_left); + mixAudioStream(leftdry, left, dleft, last_out_mixer_left, out_mixer_left); if (rightdry != null) - mixAudioStream(rightdry, right, last_out_mixer_right, + mixAudioStream(rightdry, right, dright, last_out_mixer_right, out_mixer_right); else - mixAudioStream(leftdry, right, last_out_mixer_right, + mixAudioStream(leftdry, right, dright, last_out_mixer_right, out_mixer_right); } } if (rightdry == null) { - mixAudioStream(leftdry, eff1, last_out_mixer_effect1, + mixAudioStream(leftdry, eff1, deff1, last_out_mixer_effect1, out_mixer_effect1); - mixAudioStream(leftdry, eff2, last_out_mixer_effect2, + mixAudioStream(leftdry, eff2, deff2, last_out_mixer_effect2, out_mixer_effect2); } else { - mixAudioStream(leftdry, eff1, last_out_mixer_effect1 * 0.5f, + mixAudioStream(leftdry, eff1, deff1, last_out_mixer_effect1 * 0.5f, out_mixer_effect1 * 0.5f); - mixAudioStream(leftdry, eff2, last_out_mixer_effect2 * 0.5f, + mixAudioStream(leftdry, eff2, deff2, last_out_mixer_effect2 * 0.5f, out_mixer_effect2 * 0.5f); - mixAudioStream(rightdry, eff1, last_out_mixer_effect1 * 0.5f, + mixAudioStream(rightdry, eff1, deff1, last_out_mixer_effect1 * 0.5f, out_mixer_effect1 * 0.5f); - mixAudioStream(rightdry, eff2, last_out_mixer_effect2 * 0.5f, + mixAudioStream(rightdry, eff2, deff2, last_out_mixer_effect2 * 0.5f, out_mixer_effect2 * 0.5f); } diff --git a/jdk/test/javax/sound/midi/Gervill/SoftReceiver/GetMidiDevice.java b/jdk/test/javax/sound/midi/Gervill/SoftReceiver/GetMidiDevice.java new file mode 100644 index 00000000000..8839111d8fe --- /dev/null +++ b/jdk/test/javax/sound/midi/Gervill/SoftReceiver/GetMidiDevice.java @@ -0,0 +1,48 @@ +/* + * Copyright 2009 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* @test + @summary Test SoftReceiver getMidiDevice method */ + +import javax.sound.midi.Receiver; + +import com.sun.media.sound.AudioSynthesizer; +import com.sun.media.sound.SoftReceiver; +import com.sun.media.sound.SoftSynthesizer; + +public class GetMidiDevice { + + public static void main(String[] args) throws Exception { + + AudioSynthesizer synth = new SoftSynthesizer(); + synth.openStream(null, null); + Receiver recv = synth.getReceiver(); + if (((SoftReceiver) recv).getMidiDevice() != synth) { + throw new Exception("SoftReceiver.getMidiDevice() doesn't return " + + "instance of the synthesizer"); + } + synth.close(); + } +} \ No newline at end of file diff --git a/jdk/test/javax/sound/midi/Gervill/SoftSynthesizer/TestPreciseTimestampRendering.java b/jdk/test/javax/sound/midi/Gervill/SoftSynthesizer/TestPreciseTimestampRendering.java new file mode 100644 index 00000000000..d4d55bb3f96 --- /dev/null +++ b/jdk/test/javax/sound/midi/Gervill/SoftSynthesizer/TestPreciseTimestampRendering.java @@ -0,0 +1,208 @@ +/* + * Copyright 2009 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* @test + @summary Test rendering when using precise timestamps */ + +import java.util.Arrays; +import java.util.Random; + +import javax.sound.midi.MidiChannel; +import javax.sound.midi.Receiver; +import javax.sound.midi.ShortMessage; +import javax.sound.midi.Soundbank; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; + +import com.sun.media.sound.AudioFloatConverter; +import com.sun.media.sound.AudioSynthesizer; +import com.sun.media.sound.ModelAbstractChannelMixer; +import com.sun.media.sound.ModelChannelMixer; +import com.sun.media.sound.SF2Instrument; +import com.sun.media.sound.SF2InstrumentRegion; +import com.sun.media.sound.SF2Layer; +import com.sun.media.sound.SF2LayerRegion; +import com.sun.media.sound.SF2Sample; +import com.sun.media.sound.SF2Soundbank; +import com.sun.media.sound.SimpleInstrument; +import com.sun.media.sound.SimpleSoundbank; +import com.sun.media.sound.SoftSynthesizer; + +public class TestPreciseTimestampRendering { + + public static AudioFormat format = new AudioFormat(44100, 16, 1, true, + false); + + public static SF2Soundbank createTestSoundbank() { + // Create impulse instrument + // used to measure timing of note-on playback + SF2Soundbank soundbank = new SF2Soundbank(); + float[] data = new float[100]; + Arrays.fill(data, 0); + data[0] = 1.0f; + byte[] bdata = new byte[data.length * format.getFrameSize()]; + AudioFloatConverter.getConverter(format).toByteArray(data, bdata); + + SF2Sample sample = new SF2Sample(soundbank); + sample.setName("Test Sample"); + sample.setData(bdata); + sample.setSampleRate((long) format.getSampleRate()); + sample.setOriginalPitch(69); + soundbank.addResource(sample); + + SF2Layer layer = new SF2Layer(soundbank); + layer.setName("Test Layer"); + soundbank.addResource(layer); + SF2LayerRegion region = new SF2LayerRegion(); + region.setSample(sample); + layer.getRegions().add(region); + + SF2Instrument ins = new SF2Instrument(soundbank); + ins.setName("Test Instrument"); + soundbank.addInstrument(ins); + SF2InstrumentRegion insregion = new SF2InstrumentRegion(); + insregion.setLayer(layer); + ins.getRegions().add(insregion); + + return soundbank; + } + + public static Soundbank createTestSoundbankWithChannelMixer() { + SF2Soundbank soundbank = createTestSoundbank(); + + SimpleSoundbank simplesoundbank = new SimpleSoundbank(); + SimpleInstrument simpleinstrument = new SimpleInstrument() { + + public ModelChannelMixer getChannelMixer(MidiChannel channel, + AudioFormat format) { + return new ModelAbstractChannelMixer() { + boolean active = true; + + public boolean process(float[][] buffer, int offset, int len) { + for (int i = 0; i < buffer.length; i++) { + float[] cbuffer = buffer[i]; + for (int j = 0; j < cbuffer.length; j++) { + cbuffer[j] = -cbuffer[j]; + } + } + return active; + } + + public void stop() { + active = false; + } + }; + } + + }; + simpleinstrument.add(soundbank.getInstruments()[0]); + simplesoundbank.addInstrument(simpleinstrument); + + return simplesoundbank; + } + + public static void main(String[] args) throws Exception { + test(createTestSoundbank()); + test(createTestSoundbankWithChannelMixer()); + } + + public static void test(Soundbank soundbank) throws Exception { + + // Create instance of synthesizer using the testing soundbank above + AudioSynthesizer synth = new SoftSynthesizer(); + AudioInputStream stream = synth.openStream(format, null); + synth.unloadAllInstruments(synth.getDefaultSoundbank()); + synth.loadAllInstruments(soundbank); + Receiver recv = synth.getReceiver(); + + // Set volume to max and turn reverb off + ShortMessage reverb_off = new ShortMessage(); + reverb_off.setMessage(ShortMessage.CONTROL_CHANGE, 91, 0); + recv.send(reverb_off, -1); + ShortMessage full_volume = new ShortMessage(); + full_volume.setMessage(ShortMessage.CONTROL_CHANGE, 7, 127); + recv.send(full_volume, -1); + + Random random = new Random(3485934583945l); + + // Create random timestamps + long[] test_timestamps = new long[30]; + for (int i = 1; i < test_timestamps.length; i++) { + test_timestamps[i] = i * 44100 + + (int) (random.nextDouble() * 22050.0); + } + + // Send midi note on message to synthesizer + for (int i = 0; i < test_timestamps.length; i++) { + ShortMessage midi_on = new ShortMessage(); + midi_on.setMessage(ShortMessage.NOTE_ON, 69, 127); + recv.send(midi_on, + (long) ((test_timestamps[i] / 44100.0) * 1000000.0)); + } + + // Measure timing from rendered audio + float[] fbuffer = new float[100]; + byte[] buffer = new byte[fbuffer.length * format.getFrameSize()]; + long firsts = -1; + int counter = 0; + long s = 0; + long max_jitter = 0; + outerloop: for (int k = 0; k < 10000000; k++) { + stream.read(buffer); + AudioFloatConverter.getConverter(format).toFloatArray(buffer, + fbuffer); + for (int i = 0; i < fbuffer.length; i++) { + if (fbuffer[i] != 0) { + if (firsts == -1) + firsts = s; + + long measure_time = (s - firsts); + long predicted_time = test_timestamps[counter]; + + long jitter = Math.abs(measure_time - predicted_time); + + if (jitter > 10) + max_jitter = jitter; + + counter++; + if (counter == test_timestamps.length) + break outerloop; + } + s++; + } + } + synth.close(); + + if (counter == 0) + throw new Exception("Nothing was measured!"); + + if (max_jitter != 0) { + throw new Exception("Jitter has occurred! " + + "(max jitter = " + max_jitter + ")"); + } + + } + +}