28package com.jogamp.openal.util;
30import java.nio.ByteBuffer;
31import java.nio.FloatBuffer;
33import com.jogamp.common.av.AudioFormat;
34import com.jogamp.common.av.AudioSink;
35import com.jogamp.common.av.PTS;
36import com.jogamp.common.nio.Buffers;
37import com.jogamp.common.os.Platform;
38import com.jogamp.common.util.InterruptSource;
39import com.jogamp.common.util.InterruptedRuntimeException;
40import com.jogamp.common.util.SourcedInterruptedException;
41import com.jogamp.common.util.WorkerThread;
42import com.jogamp.openal.sound3d.Context;
43import com.jogamp.openal.sound3d.Device;
44import com.jogamp.openal.sound3d.Source;
54 private static final boolean DEBUG =
false;
57 private static final float PI = 3.14159265358979323846f;
60 private static final float TWO_PI = 2f * PI;
62 private static final float EPSILON = 1.1920929E-7f;
64 private static final float SHORT_MAX = 32767.0f;
69 private final Object stateLock =
new Object();
70 private volatile float audioAmplitude = 1.0f;
71 private volatile float audioFreq =
MIDDLE_C;
72 private volatile int nextAudioPTS = 0;
73 private SynthWorker streamWorker;
80 streamWorker =
new SynthWorker();
94 public float getFreq() {
return audioFreq; }
97 audioAmplitude = Math.min(1.0f, Math.max(0.0f, a));
102 public int getLatency() {
return null != streamWorker ? streamWorker.frameDuration : 2*AudioSink.DefaultFrameDuration; }
105 synchronized( stateLock ) {
106 if(
null == streamWorker ) {
107 streamWorker =
new SynthWorker();
109 streamWorker.doResume();
114 synchronized( stateLock ) {
115 if(
null != streamWorker ) {
116 streamWorker.doPause(
true);
122 synchronized( stateLock ) {
123 if(
null != streamWorker ) {
124 streamWorker.doStop();
133 synchronized( stateLock ) {
134 if(
null != streamWorker ) {
135 return streamWorker.isPlaying();
142 synchronized( stateLock ) {
143 if(
null != streamWorker ) {
144 return streamWorker.isRunning();
156 synchronized( stateLock ) {
157 final int pts =
getPTS().getLast();
159 return getClass().getSimpleName()+
"[f "+audioFreq+
", a "+audioAmplitude+
", latency "+
getLatency()+
164 private static ByteBuffer allocate(
final int size) {
166 return Buffers.newDirectByteBuffer(size);
170 private final boolean useFloat32SampleType;
171 private final int bytesPerSample;
172 private final AudioFormat audioFormat;
173 private ByteBuffer sampleBuffer;
174 private int frameDuration;
175 private int audioQueueLimit;
177 private float lastFreq;
178 private float nextSin;
179 private boolean upSin;
180 private int nextStep;
182 private final WorkerThread.StateCallback stateCB = (
final WorkerThread
self,
final WorkerThread.StateCallback.State cause) -> {
185 nextAudioPTS = (int)Platform.currentMillis();
191 nextAudioPTS = (int)Platform.currentMillis();
200 private final WorkerThread.Callback action = (
final WorkerThread aaa) -> {
203 final WorkerThread wt =
new WorkerThread(
null,
null,
true , action, stateCB);
216 final AudioFormat f32 =
new AudioFormat(audioSink.
getPreferredFormat().sampleRate, 4<<3, 1,
true ,
217 false ,
false ,
true );
219 useFloat32SampleType =
true;
223 useFloat32SampleType =
false;
225 audioFormat =
new AudioFormat(audioSink.
getPreferredFormat().sampleRate, bytesPerSample<<3, 1,
true ,
226 true ,
false ,
true );
228 System.err.println(
"OpenAL float32 supported: "+useFloat32SampleType);
230 sampleBuffer = allocate( audioFormat.getDurationsByteSize(30/1000f) );
234 audioQueueLimit = Math.max( 16, Math.min(3*AudioSink.DefaultFrameDuration, 3*Math.round( 1000f*audioSink.
getDefaultLatency() ) ) );
236 audioSink.
init(audioFormat, frameDuration, audioQueueLimit);
237 frameDuration = Math.round( 1000f*audioSink.
getLatency() );
247 private final int findNextStep(
final boolean upSin,
final float nextSin,
final float freq,
final int sampleRate,
final int sampleCount) {
248 final float sample_step = ( TWO_PI * freq ) / sampleRate;
250 float s_diff = Float.MAX_VALUE;
254 for(
int i=0; i < sampleCount && s_diff >= EPSILON ; ++i) {
255 final float s1 = (float) Math.sin( sample_step * i );
256 final float s_d = Math.abs(nextSin - s1);
257 if( s_d < s_diff && ( ( upSin && s1 >= s0 ) || ( !upSin && s1 < s0 ) ) ) {
265 System.err.printf(
"%nBest: %d/[%d..%d]: s %f / %f (up %b), s_diff %f%n", i_best, 0, sampleCount, s_best, nextSin, upSin, s_diff);
270 private final void enqueueWave() {
272 final float freq = audioFreq;
273 final float amp = audioAmplitude;
275 final float period = 1.0f / freq;
276 final float sample_step = ( TWO_PI * freq ) / audioFormat.sampleRate;
278 final float duration = frameDuration / 1000.0f;
279 final int sample_count = (
int)( duration * audioFormat.sampleRate );
281 final boolean overflow;
282 final boolean changedFreq;
283 if( Math.abs( freq - lastFreq ) >= EPSILON ) {
287 nextStep = findNextStep(upSin, nextSin, freq, audioFormat.sampleRate, sample_count);
290 if( nextStep + sample_count >= Integer.MAX_VALUE/1000 ) {
291 nextStep = findNextStep(upSin, nextSin, freq, audioFormat.sampleRate, sample_count);
299 if( changedFreq || overflow ) {
300 final float wave_count = duration / period;
301 System.err.printf(
"%nFreq %f Hz, period %f [ms], waves %.2f, duration %f [ms], sample[count %d, rate %d, step %f, next[up %b, sin %f, step %d]]%n", freq,
302 1000.0*period, wave_count, 1000.0*duration, sample_count, audioFormat.sampleRate, sample_step, upSin, nextSin, nextStep);
307 if( sampleBuffer.capacity() < bytesPerSample*sample_count ) {
309 System.err.printf(
"SampleBuffer grow: %d -> %d%n", sampleBuffer.capacity(), bytesPerSample*sample_count);
311 sampleBuffer = allocate(bytesPerSample*sample_count);
317 if( useFloat32SampleType ) {
318 final FloatBuffer f32sb = sampleBuffer.asFloatBuffer();
319 final int l = nextStep;
320 for(i=l; i<l+sample_count; ++i) {
321 s = (float) Math.sin( sample_step * i );
325 final int l = nextStep;
326 for(i=l; i<l+sample_count; ++i) {
327 s = (float) Math.sin( sample_step * i );
328 final short s16 = (short)( SHORT_MAX * s * amp );
329 sampleBuffer.put( (
byte) ( s16 & 0xff ) );
330 sampleBuffer.put( (
byte) ( ( s16 >>> 8 ) & 0xff ) );
334 nextSin = (float) Math.sin( sample_step * nextStep );
335 upSin = nextSin >= s;
337 sampleBuffer.rewind();
338 audioSink.
enqueueData(nextAudioPTS, sampleBuffer, sample_count*bytesPerSample);
339 sampleBuffer.clear();
340 nextAudioPTS += frameDuration;
343 public final synchronized void doPause(
final boolean waitUntilDone) {
344 wt.pause(waitUntilDone);;
346 public final synchronized void doResume() {
349 public final synchronized void doStop() {
353 public final boolean isRunning() {
return wt.isRunning(); }
354 public final boolean isPlaying() {
return wt.isActive(); }
This class provides a Sound3D Context associated with a specified device.
This class provides a handle to a specific audio device.
This class is used to represent sound-producing objects in the Sound3D environment.
final AudioFrame enqueueData(final int pts, final ByteBuffer bytes, final int byteCount)
final boolean init(final AudioFormat requestedFormat, final int frameDurationHint, final int queueSize)
float getDefaultLatency()
final boolean isSupported(final AudioFormat format)
final Context getContext()
Return this instance's OpenAL Context.
final AudioFormat getPreferredFormat()
final Device getDevice()
Return this instance's OpenAL Device.
final Source getSource()
Return this instance's OpenAL Source.
A continuous simple off-thread mutable sine wave synthesizer.
SimpleSineSynth(final Device device)
int getLatency()
Returns latency or frame-duration in milliseconds.
final Source getSource()
Return this instance's OpenAL Source.
void setFreq(final float f)
final Device getDevice()
Return this instance's OpenAL Device.
final Context getContext()
Return this instance's OpenAL Context.
void setAmplitude(final float a)
static final float MIDDLE_C