Can only play one sound clip at a time - java

I wrote a custom sound system for my game, but if two sounds are requested to play within a few ms of eachother only one sound clip will play.
I tried running the playback on a new thread like this but it did not work.
No exceptions are being thrown, it just wont play both sounds.
Thread one = new Thread() {
public void run() {
try {
CustomSound.playSound(id, loop, dist);
} catch (Exception e) {
e.printStackTrace();
}
}
};
Here is the sound player class
public class CustomSound {
/*
* Directory of your sound files
* format is WAV
*/
private static final String DIRECTORY = sign.signlink.findcachedir()+"audio/effects/";
/*
* Current volume state
* 36 chosen for default 50% volume state
*/
public static float settingModifier = 70f;
/*
* Current volume state
*/
public static boolean isMuted;
/*
* Clips
*/
private static Clip[] clipIndex = null;
/*
* Get number of files in directory
*/
private static final int getDirectoryLength() {
return new File(DIRECTORY).list().length;
}
/**
* Loads the sound clips into memory
* during startup to prevent lag if loading
* them during runtime.
**/
public static void preloadSounds() {
clipIndex = new Clip[getDirectoryLength()];
int counter = 0;
for (int i = 0; i < clipIndex.length; i++) {
try {
File f = new File(DIRECTORY+"sound "+i+".wav");
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(f);
clipIndex[i] = AudioSystem.getClip();
clipIndex[i].open(audioInputStream);
counter++;
} catch (MalformedURLException e) {
System.out.println("Sound effect not found: "+i);
e.printStackTrace();
return;
} catch (UnsupportedAudioFileException e) {
System.out.println("Unsupported format for sound: "+i);
return;
} catch (LineUnavailableException e) {
e.printStackTrace();
return;
} catch (Exception e) {
e.printStackTrace();
return;
}
}
System.out.println("Succesfully loaded: "+counter+" custom sound clips.");
}
/**
* Plays a sound
* #param soundID - The ID of the sound
* #param loop - How many times to loop this sound
* #param distanceFromSource - The distance from the source in tiles
*/
public static void playSound(final int soundID, int loop, int distanceFromSource) {
try {
if (!isMuted) {
clipIndex[soundID].setFramePosition(0);
applyVolumeSetting(clipIndex[soundID], getDistanceModifier(distanceFromSource)*settingModifier);
if (loop == 1 || loop == 0) {
clipIndex[soundID].start();
} else {
clipIndex[soundID].loop(loop);
}
/* shows how to close line when clip is finished playing
clipIndex[soundID].addLineListener(new LineListener() {
public void update(LineEvent myLineEvent) {
if (myLineEvent.getType() == LineEvent.Type.STOP)
clipIndex[soundID].close();
}
});
*/
}
} catch (Exception e) {
System.out.println("Error please report: ");
e.printStackTrace();
}
}
/**
* Applies volume setting to the clip
* #param line - the Clip to adjust volume setting for
* #param volume - the volume percentage (0-100)
* #return - the volume with applied setting
*/
public static float applyVolumeSetting(Clip line, double volume) {
//System.out.println("Modifying volume to "+volume);
if (volume > 100.0) volume = 100.0;
if (volume >= 0.0) {
FloatControl ctrl = null;
try {
ctrl = (FloatControl)(line.getControl(FloatControl.Type.MASTER_GAIN));
} catch (IllegalArgumentException iax1) {
try {
ctrl = (FloatControl)(line.getControl(FloatControl.Type.VOLUME));
} catch (IllegalArgumentException iax2) {
System.out.println("Controls.setVolume() not supported.");
return -1;
}
}
float minimum = ctrl.getMinimum();
float maximum = ctrl.getMaximum();
float newValue = (float)(minimum + volume * (maximum - minimum) / 100.0F);
//System.out.println("System min: " + minimum);
//System.out.println("System max: " + maximum);
if (newValue <= ctrl.getMinimum())
newValue = ctrl.getMinimum();
if (newValue >= ctrl.getMaximum())
newValue = ctrl.getMaximum();
ctrl.setValue(newValue);
//System.out.println("Setting modifier = " + volume);
//System.out.println("New value = " + newValue);
return newValue;
}
return -1;
}
/**
* Calculates tile distance modifier
* #param tileDistance - distance in tiles from source
* #return - the distance modifier
*/
public static float getDistanceModifier(int tileDistance) {
if (tileDistance <= 0) {
tileDistance = 0;
}
if (tileDistance >= 10) {
tileDistance = 10;
}
float distanceModifier = 0;
if (tileDistance == 10)
distanceModifier = 0.40f;
if (tileDistance == 9)
distanceModifier = 0.55f;
if (tileDistance == 8)
distanceModifier = 0.60f;
if (tileDistance == 7)
distanceModifier = 0.65f;
if (tileDistance == 6)
distanceModifier = 0.70f;
if (tileDistance == 5)
distanceModifier = 0.75f;
if (tileDistance == 4)
distanceModifier = 0.80f;
if (tileDistance == 3)
distanceModifier = 0.85f;
if (tileDistance == 2)
distanceModifier = 0.90f;
if (tileDistance == 1)
distanceModifier = 0.95f;
if (tileDistance == 0)
distanceModifier = 1.00f;
return distanceModifier;
}
}

When I tested your code on my Windows machine, I had no problems playing two different sounds in short succession:
public static void main(String[] args) throws Exception {
CustomSound.preloadSounds();
CustomSound.playSound(0, 0, 0);
CustomSound.playSound(1, 0, 0);
Thread.sleep(5000);
}
Note however, that DataLine#start() is an asynchronous call. That could be related to your problem.
Also, according to the documentation of DataLine#start(),
If invoked on a line that is already running, this method does nothing.
If that is your problem, and you want to play the same sound twice simultaneously, one possible solution would be to get another Clip instance that plays the same sound and start that.
However I'm no expert at Java's sound API so there might a more efficient way.

Related

DHT22 Sensor + pi4j + java

read temperature from DHT11, using pi4j
I the tried the code in the following link with prerequisites :
Java 1.8.0_65
pi4j 1.1
Raspberry Pi 3 Model B
DHT22 Temperature sensor.
Here when I am trying to execute the following both codes available in the link I am facing the issue regarding the LibPins in the code
public DHT11(int pin) {
final GpioController gpio = GpioFactory.getInstance();
dht11Pin = gpio.provisionDigitalMultipurposePin(LibPins.getPin(pin),
PinMode.DIGITAL_INPUT, PinPullResistance.PULL_UP);
}
and in the other code snippet I am getting the output as "Data not good,Skip"
But for the second code in the link I was getting the output for few set of readings and after its outputting as "Data not good,Skip".
I am working on reading the temperature connected to pi gpio pins using java and pi4j library.
Thanks in Advance.
I have had luck with this (modified from another thread).
You'll also need to install wiring pi sudo apt-get install wiringpi
It should be noted that this I put this class into it's own thread and loop it by calling run(). It then stuffs the variables into local state and I can reference them in my main class.
import com.pi4j.wiringpi.Gpio;
import com.pi4j.wiringpi.GpioUtil;
public class DHT22 implements Runnable {
private static final int maxTimings = 85;
private final int[] dht22_dat = {0, 0, 0, 0, 0};
private float temperature = 9999;
private float humidity = 9999;
boolean shuttingDown = false;
public DHT22() {
// setup wiringPi
if (Gpio.wiringPiSetup() == -1) {
System.out.println(" ==>> GPIO SETUP FAILED");
return;
}
GpioUtil.export(3, GpioUtil.DIRECTION_OUT);
}
private int pollDHT22() {
int lastState = Gpio.HIGH;
int j = 0;
dht22_dat[0] = dht22_dat[1] = dht22_dat[2] = dht22_dat[3] = dht22_dat[4] = 0;
int pinNumber = 16;
Gpio.pinMode(pinNumber, Gpio.OUTPUT);
Gpio.digitalWrite(pinNumber, Gpio.LOW);
Gpio.delay(18);
Gpio.digitalWrite(pinNumber, Gpio.HIGH);
Gpio.pinMode(pinNumber, Gpio.INPUT);
for (int i = 0; i < maxTimings; i++) {
int counter = 0;
while (Gpio.digitalRead(pinNumber) == lastState) {
counter++;
Gpio.delayMicroseconds(1);
if (counter == 255) {
break;
}
}
lastState = Gpio.digitalRead(pinNumber);
if (counter == 255) {
break;
}
/* ignore first 3 transitions */
if (i >= 4 && i % 2 == 0) {
/* shove each bit into the storage bytes */
dht22_dat[j / 8] <<= 1;
if (counter > 16) {
dht22_dat[j / 8] |= 1;
}
j++;
}
}
return j;
}
private void refreshData() {
int pollDataCheck = pollDHT22();
if (pollDataCheck >= 40 && checkParity()) {
final float newHumidity = (float) ((dht22_dat[0] << 8) + dht22_dat[1]) / 10;
final float newTemperature = (float) (((dht22_dat[2] & 0x7F) << 8) + dht22_dat[3]) / 10;
if (humidity == 9999 || ((newHumidity < humidity + 40) && (newHumidity > humidity - 40))) {
humidity = newHumidity;
if (humidity > 100) {
humidity = dht22_dat[0]; // for DHT22
}
}
if (temperature == 9999 || ((newTemperature < temperature + 40) && (newTemperature > temperature - 40))) {
temperature = (float) (((dht22_dat[2] & 0x7F) << 8) + dht22_dat[3]) / 10;
if (temperature > 125) {
temperature = dht22_dat[2]; // for DHT22
}
if ((dht22_dat[2] & 0x80) != 0) {
temperature = -temperature;
}
}
}
}
float getHumidity() {
if (humidity == 9999) {
return 0;
}
return humidity;
}
#SuppressWarnings("unused")
float getTemperature() {
if (temperature == 9999) {
return 0;
}
return temperature;
}
float getTemperatureInF() {
if (temperature == 9999) {
return 32;
}
return temperature * 1.8f + 32;
}
private boolean checkParity() {
return dht22_dat[4] == (dht22_dat[0] + dht22_dat[1] + dht22_dat[2] + dht22_dat[3] & 0xFF);
}
#Override
public void run() {
while (!shuttingDown) {
refreshData();
}
}
}
This version uses the java nanosecond timing to read the binary data. It uses Pi4J which uses WiringPi. I hope it is helpful. (https://github.com/dougculnane/java-pi-thing)
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.pi4j.io.gpio.Pin;
import com.pi4j.io.gpio.RaspiPin;
import com.pi4j.wiringpi.Gpio;
/**
* Implements the DHT22 / AM2302 reading in Java using Pi4J.
*
* See (AM2302.pdf) specksheet for details of timings.
*
* #author Doug Culnane
*/
public class DHT22 {
/**
* Time in nanoseconds to separate ZERO and ONE signals.
*/
private static final int LONGEST_ZERO = 50000;
/**
* PI4J Pin number.
*/
private int pinNumber;
/**
* 40 bit Data from sensor
*/
private byte[] data = null;
/**
* Value of last successful humidity reading.
*/
private Double humidity = null;
/**
* Value of last successful temperature reading.
*/
private Double temperature = null;
/**
* Last read attempt
*/
private Long lastRead = null;
/**
* Constructor with pin used for signal. See PI4J and WiringPI for
* pin numbering systems.....
*
* #param pin
*/
public DHT22(Pin pin) {
pinNumber = pin.getAddress();
}
/**
* Communicate with sensor to get new reading data.
*
* #throws Exception if failed to successfully read data.
*/
private void getData() throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
ReadSensorFuture readSensor = new ReadSensorFuture();
Future<byte[]> future = executor.submit(readSensor);
// Reset data
data = new byte[5];
try {
data = future.get(3, TimeUnit.SECONDS);
readSensor.close();
} catch (TimeoutException e) {
readSensor.close();
future.cancel(true);
executor.shutdown();
throw e;
}
readSensor.close();
executor.shutdown();
}
/**
* Make a new sensor reading.
*
* #throws Exception
*/
public boolean read() throws Exception {
checkLastReadDelay();
lastRead = System.currentTimeMillis();
getData();
checkParity();
humidity = getReadingValueFromBytes(data[0], data[1]);
temperature = getReadingValueFromBytes(data[2], data[3]);
lastRead = System.currentTimeMillis();
return true;
}
private void checkLastReadDelay() throws Exception {
if (Objects.nonNull(lastRead)) {
if (lastRead > System.currentTimeMillis() - 2000) {
throw new Exception("Last read was under 2 seconds ago. Please wait longer between reads!");
}
}
}
private double getReadingValueFromBytes(final byte hi, final byte low) {
ByteBuffer bb = ByteBuffer.allocate(2);
bb.order(ByteOrder.BIG_ENDIAN);
bb.put(hi);
bb.put(low);
short shortVal = bb.getShort(0);
return new Double(shortVal) / 10;
}
private void checkParity() throws ParityChheckException {
if (!(data[4] == (data[0] + data[1] + data[2] + data[3] & 0xFF))) {
throw new ParityChheckException();
}
}
public Double getHumidity() {
return humidity;
}
public Double getTemperature() {
return temperature;
}
/**
* Run from command line to loop and make readings.
* #throws IOException
*/
public static void main(String[] args) throws IOException {
System.out.println("Starting DHT22");
if (Gpio.wiringPiSetup() == -1) {
System.out.println("GPIO wiringPiSetup Failed!");
return;
}
DHT22 dht22 = new DHT22(RaspiPin.GPIO_05);
int LOOP_SIZE = 10;
int countSuccess = 0;
for (int i=0; i < LOOP_SIZE; i++) {
try {
Thread.sleep(3000);
System.out.println();
dht22.read();
System.out.println("Humidity=" + dht22.getHumidity() +
"%, Temperature=" + dht22.getTemperature() + "*C");
countSuccess++;
} catch (TimeoutException e) {
System.out.println("ERROR: " + e);
} catch (Exception e) {
System.out.println("ERROR: " + e);
}
}
System.out.println("Read success rate: "+ countSuccess + " / " + LOOP_SIZE);
System.out.println("Ending DHT22");
}
/**
* Callable Future for reading sensor. Allows timeout if it gets stuck.
*/
private class ReadSensorFuture implements Callable<byte[]>, Closeable {
private boolean keepRunning = true;
public ReadSensorFuture() {
Gpio.pinMode(pinNumber, Gpio.OUTPUT);
Gpio.digitalWrite(pinNumber, Gpio.HIGH);
}
#Override
public byte[] call() throws Exception {
// do expensive (slow) stuff before we start.
byte[] data = new byte[5];
long startTime = System.nanoTime();
sendStartSignal();
waitForResponseSignal();
for (int i = 0; i < 40; i++) {
while (keepRunning && Gpio.digitalRead(pinNumber) == Gpio.LOW) {
}
startTime = System.nanoTime();
while (keepRunning && Gpio.digitalRead(pinNumber) == Gpio.HIGH) {
}
long timeHight = System.nanoTime() - startTime;
data[i / 8] <<= 1;
if ( timeHight > LONGEST_ZERO) {
data[i / 8] |= 1;
}
}
return data;
}
private void sendStartSignal() {
// Send start signal.
Gpio.pinMode(pinNumber, Gpio.OUTPUT);
Gpio.digitalWrite(pinNumber, Gpio.LOW);
Gpio.delay(1);
Gpio.digitalWrite(pinNumber, Gpio.HIGH);
}
/**
* AM2302 will pull low 80us as response signal, then
* AM2302 pulls up 80us for preparation to send data.
*/
private void waitForResponseSignal() {
Gpio.pinMode(pinNumber, Gpio.INPUT);
while (keepRunning && Gpio.digitalRead(pinNumber) == Gpio.HIGH) {
}
while (keepRunning && Gpio.digitalRead(pinNumber) == Gpio.LOW) {
}
while (keepRunning && Gpio.digitalRead(pinNumber) == Gpio.HIGH) {
}
}
#Override
public void close() throws IOException {
keepRunning = false;
// Set pin high for end of transmission.
Gpio.pinMode(pinNumber, Gpio.OUTPUT);
Gpio.digitalWrite(pinNumber, Gpio.HIGH);
}
}
private class ParityChheckException extends Exception {
private static final long serialVersionUID = 1L;
}
}

Using getInstance() in java class

I am trying to use a third party java class code and I get this error
Cannot resolve method "getInstance()"
and
Cannot resolve symbol "INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH"
Does anyone know how to solve this?
Error Image:
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.hardware.input.InputManager;
import android.os.SystemClock;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import java.util.HashMap;
import java.util.Map;
/**
* Command that sends key events to the device, either by their keycode, or by
* desired character output.
*/
public class Input {
private static final String TAG = "Input";
private static final String INVALID_ARGUMENTS = "Error: Invalid arguments for command: ";
private static final Map<String, Integer> SOURCES = new HashMap<String, Integer>() {{
put("keyboard", InputDevice.SOURCE_KEYBOARD);
put("dpad", InputDevice.SOURCE_DPAD);
put("gamepad", InputDevice.SOURCE_GAMEPAD);
put("touchscreen", InputDevice.SOURCE_TOUCHSCREEN);
put("mouse", InputDevice.SOURCE_MOUSE);
put("stylus", InputDevice.SOURCE_STYLUS);
put("trackball", InputDevice.SOURCE_TRACKBALL);
put("touchpad", InputDevice.SOURCE_TOUCHPAD);
put("touchnavigation", InputDevice.SOURCE_TOUCH_NAVIGATION);
put("joystick", InputDevice.SOURCE_JOYSTICK);
}};
/**
* Command-line entry point.
*
* #param args The command-line arguments
*/
public static void main(String[] args) {
(new Input()).run(args);
}
private void run(String[] args) {
if (args.length < 1) {
showUsage();
return;
}
int index = 0;
String command = args[index];
int inputSource = InputDevice.SOURCE_UNKNOWN;
if (SOURCES.containsKey(command)) {
inputSource = SOURCES.get(command);
index++;
command = args[index];
}
final int length = args.length - index;
try {
if (command.equals("text")) {
if (length == 2) {
inputSource = getSource(inputSource, InputDevice.SOURCE_KEYBOARD);
sendText(inputSource, args[index+1]);
return;
}
} else if (command.equals("keyevent")) {
if (length >= 2) {
final boolean longpress = "--longpress".equals(args[index + 1]);
final int start = longpress ? index + 2 : index + 1;
inputSource = getSource(inputSource, InputDevice.SOURCE_KEYBOARD);
if (length > start) {
for (int i = start; i < length; i++) {
int keyCode = KeyEvent.keyCodeFromString(args[i]);
if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
keyCode = KeyEvent.keyCodeFromString("KEYCODE_" + args[i]);
}
sendKeyEvent(inputSource, keyCode, longpress);
}
return;
}
}
} else if (command.equals("tap")) {
if (length == 3) {
inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
sendTap(inputSource, Float.parseFloat(args[index+1]),
Float.parseFloat(args[index+2]));
return;
}
} else if (command.equals("swipe")) {
int duration = -1;
inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
switch (length) {
case 6:
duration = Integer.parseInt(args[index+5]);
case 5:
sendSwipe(inputSource,
Float.parseFloat(args[index+1]), Float.parseFloat(args[index+2]),
Float.parseFloat(args[index+3]), Float.parseFloat(args[index+4]),
duration);
return;
}
} else if (command.equals("press")) {
inputSource = getSource(inputSource, InputDevice.SOURCE_TRACKBALL);
if (length == 1) {
sendTap(inputSource, 0.0f, 0.0f);
return;
}
} else if (command.equals("roll")) {
inputSource = getSource(inputSource, InputDevice.SOURCE_TRACKBALL);
if (length == 3) {
sendMove(inputSource, Float.parseFloat(args[index+1]),
Float.parseFloat(args[index+2]));
return;
}
} else {
System.err.println("Error: Unknown command: " + command);
showUsage();
return;
}
} catch (NumberFormatException ex) {
}
System.err.println(INVALID_ARGUMENTS + command);
showUsage();
}
/**
* Convert the characters of string text into key event's and send to
* device.
*
* #param text is a string of characters you want to input to the device.
*/
private void sendText(int source, String text) {
StringBuffer buff = new StringBuffer(text);
boolean escapeFlag = false;
for (int i=0; i<buff.length(); i++) {
if (escapeFlag) {
escapeFlag = false;
if (buff.charAt(i) == 's') {
buff.setCharAt(i, ' ');
buff.deleteCharAt(--i);
}
}
if (buff.charAt(i) == '%') {
escapeFlag = true;
}
}
char[] chars = buff.toString().toCharArray();
KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
KeyEvent[] events = kcm.getEvents(chars);
for(int i = 0; i < events.length; i++) {
KeyEvent e = events[i];
if (source != e.getSource()) {
e.setSource(source);
}
injectKeyEvent(e);
}
}
private void sendKeyEvent(int inputSource, int keyCode, boolean longpress) {
long now = SystemClock.uptimeMillis();
injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource));
if (longpress) {
injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 1, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_LONG_PRESS,
inputSource));
}
injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource));
}
private void sendTap(int inputSource, float x, float y) {
long now = SystemClock.uptimeMillis();
injectMotionEvent(inputSource, MotionEvent.ACTION_DOWN, now, x, y, 1.0f);
injectMotionEvent(inputSource, MotionEvent.ACTION_UP, now, x, y, 0.0f);
}
private void sendSwipe(int inputSource, float x1, float y1, float x2, float y2, int duration) {
if (duration < 0) {
duration = 300;
}
long now = SystemClock.uptimeMillis();
injectMotionEvent(inputSource, MotionEvent.ACTION_DOWN, now, x1, y1, 1.0f);
long startTime = now;
long endTime = startTime + duration;
while (now < endTime) {
long elapsedTime = now - startTime;
float alpha = (float) elapsedTime / duration;
injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, now, lerp(x1, x2, alpha),
lerp(y1, y2, alpha), 1.0f);
now = SystemClock.uptimeMillis();
}
injectMotionEvent(inputSource, MotionEvent.ACTION_UP, now, x2, y2, 0.0f);
}
/**
* Sends a simple zero-pressure move event.
*
* #param inputSource the InputDevice.SOURCE_* sending the input event
* #param dx change in x coordinate due to move
* #param dy change in y coordinate due to move
*/
private void sendMove(int inputSource, float dx, float dy) {
long now = SystemClock.uptimeMillis();
injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, now, dx, dy, 0.0f);
}
private void injectKeyEvent(KeyEvent event) {
Log.i(TAG, "injectKeyEvent: " + event);
InputManager.getInstance().injectInputEvent(event,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
}
private int getInputDeviceId(int inputSource) {
final int DEFAULT_DEVICE_ID = 0;
int[] devIds = InputDevice.getDeviceIds();
for (int devId : devIds) {
InputDevice inputDev = InputDevice.getDevice(devId);
if (inputDev.supportsSource(inputSource)) {
return devId;
}
}
return DEFAULT_DEVICE_ID;
}
/**
* Builds a MotionEvent and injects it into the event stream.
*
* #param inputSource the InputDevice.SOURCE_* sending the input event
* #param action the MotionEvent.ACTION_* for the event
* #param when the value of SystemClock.uptimeMillis() at which the event happened
* #param x x coordinate of event
* #param y y coordinate of event
* #param pressure pressure of event
*/
private void injectMotionEvent(int inputSource, int action, long when, float x, float y, float pressure) {
final float DEFAULT_SIZE = 1.0f;
final int DEFAULT_META_STATE = 0;
final float DEFAULT_PRECISION_X = 1.0f;
final float DEFAULT_PRECISION_Y = 1.0f;
final int DEFAULT_EDGE_FLAGS = 0;
MotionEvent event = MotionEvent.obtain(when, when, action, x, y, pressure, DEFAULT_SIZE,
DEFAULT_META_STATE, DEFAULT_PRECISION_X, DEFAULT_PRECISION_Y,
getInputDeviceId(inputSource), DEFAULT_EDGE_FLAGS);
event.setSource(inputSource);
Log.i(TAG, "injectMotionEvent: " + event);
InputManager.getInstance().injectInputEvent(event,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
}
private static final float lerp(float a, float b, float alpha) {
return (b - a) * alpha + a;
}
private static final int getSource(int inputSource, int defaultSource) {
return inputSource == InputDevice.SOURCE_UNKNOWN ? defaultSource : inputSource;
}
private void showUsage() {
System.err.println("Usage: input [<source>] <command> [<arg>...]");
System.err.println();
System.err.println("The sources are: ");
for (String src : SOURCES.keySet()) {
System.err.println(" " + src);
}
System.err.println();
System.err.println("The commands and default sources are:");
System.err.println(" text <string> (Default: touchscreen)");
System.err.println(" keyevent [--longpress] <key code number or name> ..."
+ " (Default: keyboard)");
System.err.println(" tap <x> <y> (Default: touchscreen)");
System.err.println(" swipe <x1> <y1> <x2> <y2> [duration(ms)]"
+ " (Default: touchscreen)");
System.err.println(" press (Default: trackball)");
System.err.println(" roll <dx> <dy> (Default: trackball)");
}
}
I suspect you're building against the SDK, not against AOSP. The getInstance() method is hidden (note the #hide here). This means the method is not included in the android.jar file applications are built against.
You'll either need to access the method via reflection, or build against AOSP.
I guess you used wrong import, can you check if you imported InputManager from correct package, and that is not error , it just compilation error as your IDE couldn't find the references

Where to declair the alarm manager for updating a watchface in ambient mode?

I'm trying to add an AlarmManager to update a watch face on half minute intervals.
This is for a ternary clock.
This is my first time programming with Java or android studio.
I'm following a guide at https://developer.android.com/training/wearables/apps/always-on.html
The guide says to "declare the alarm manager and the pending intent in the onCreate() method of your activity"
Should I use
#Override
public Engine onCreateEngine() {
return new Engine();
}
or
#Override
public Engine onCreateEngine() {
return new Engine();
}
or should I start a new method or declare it elsewhere?
Currently I'm using
private class Engine extends CanvasWatchFaceService.Engine {
final Handler mUpdateTimeHandler = new EngineHandler(this);
for most of my initialization.
This is my code without the alarm manager. The issue is that it must update during half minutes, because as balanced ternary the time should be to the nearest minute.
public class ternary extends CanvasWatchFaceService {
private static final Typeface NORMAL_TYPEFACE =
Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
private static final int MSG_UPDATE_TIME = 0;
#Override
public Engine onCreateEngine() {
return new Engine();
}
private static class EngineHandler extends Handler {
private final WeakReference<ternary.Engine> mWeakReference;
public EngineHandler(ternary.Engine reference) {
mWeakReference = new WeakReference<>(reference);
}
#Override
public void handleMessage(Message msg) {
ternary.Engine engine = mWeakReference.get();
if (engine != null) {
switch (msg.what) {
case MSG_UPDATE_TIME:
engine.handleUpdateTimeMessage();
break;
}
}
}
}
private class Engine extends CanvasWatchFaceService.Engine {
final Handler mUpdateTimeHandler = new EngineHandler(this);
boolean mRegisteredTimeZoneReceiver = false;
Paint mBackgroundPaint;
Paint mTextPaint;
boolean mAmbient;
Time mTime;
final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
mTime.clear(intent.getStringExtra("time-zone"));
mTime.setToNow();
}
};
int mTapCount;
float mXOffset;
float mYOffset;
// adjust text size
float textRatio = (float)1; // 2/3;
// make adjusted offset for hours
float hrsIndent;
float hrsIndentAdjust = textRatio * 55;
// vertical offset for multiple lines
float ySpacer = textRatio * 65;
// first run.
boolean yesFirstRun = true;
// flag for seconds
boolean yesSecs;
// prior state of yesSecs
boolean wasSecs = true;
// flag for conservation mode (no seconds in ambient)
boolean yesConcerve = false;
// flag for allowing seconds
boolean allowSecs = true;
// for execution control
boolean openGate = false;
// counter for next draw
int c = 0;
// counter for time loops
int k;
boolean drawNow = true;
// strings for draw
String hrs = "";
String mns = "";
String sks = "";
// register for milliseconds
long millis = 0;
// float for calculating trits from time.
float tim = 0;
// ints for minute and hour offsets.
int minInt = 0;
int hourInt = 0;
// lists for time to trit for loop conversions.
int [] trits3 = {9, 3, 1};
int [] trits4 = {27, 9, 3, 1};
// absolute count for trouble shooting
// long x = 0;
/**
* Whether the display supports fewer bits for each color in ambient mode. When true, we
* disable anti-aliasing in ambient mode.
*/
boolean mLowBitAmbient;
#Override
public void onCreate(SurfaceHolder holder) {
super.onCreate(holder);
setWatchFaceStyle(new WatchFaceStyle.Builder(ternary.this)
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
.setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
.setShowSystemUiTime(false)
.setAcceptsTapEvents(true)
.build());
Resources resources = ternary.this.getResources();
// shift y offset up
mYOffset = -30 + resources.getDimension(R.dimen.digital_y_offset);
mBackgroundPaint = new Paint();
mBackgroundPaint.setColor(resources.getColor(R.color.background));
mTextPaint = new Paint();
mTextPaint = createTextPaint(resources.getColor(R.color.digital_text));
mTime = new Time();
}
#Override
public void onDestroy() {
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
super.onDestroy();
}
private Paint createTextPaint(int textColor) {
Paint paint = new Paint();
paint.setColor(textColor);
paint.setTypeface(NORMAL_TYPEFACE);
paint.setAntiAlias(true);
return paint;
}
#Override
public void onVisibilityChanged(boolean visible) {
super.onVisibilityChanged(visible);
if (visible) {
registerReceiver();
// Update time zone in case it changed while we weren't visible.
mTime.clear(TimeZone.getDefault().getID());
mTime.setToNow();
} else {
unregisterReceiver();
}
// Whether the timer should be running depends on whether we're visible (as well as
// whether we're in ambient mode), so we may need to start or stop the timer.
updateTimer();
}
private void registerReceiver() {
if (mRegisteredTimeZoneReceiver) {
return;
}
mRegisteredTimeZoneReceiver = true;
IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
ternary.this.registerReceiver(mTimeZoneReceiver, filter);
}
private void unregisterReceiver() {
if (!mRegisteredTimeZoneReceiver) {
return;
}
mRegisteredTimeZoneReceiver = false;
ternary.this.unregisterReceiver(mTimeZoneReceiver);
}
#Override
public void onApplyWindowInsets(WindowInsets insets) {
super.onApplyWindowInsets(insets);
// Load resources that have alternate values for round watches.
Resources resources = ternary.this.getResources();
boolean isRound = insets.isRound();
// shift offset 75 to the right
mXOffset = 75 + resources.getDimension(isRound
? R.dimen.digital_x_offset_round : R.dimen.digital_x_offset);
float textSize = resources.getDimension(isRound
? R.dimen.digital_text_size_round : R.dimen.digital_text_size);
// adjust hrs Indent to MXOffset
hrsIndent = hrsIndentAdjust + mXOffset;
// adjust size to textRatio
mTextPaint.setTextSize(textSize * textRatio );
}
#Override
public void onPropertiesChanged(Bundle properties) {
super.onPropertiesChanged(properties);
mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
}
#Override
public void onTimeTick() {
super.onTimeTick();
invalidate();
}
#Override
public void onAmbientModeChanged(boolean inAmbientMode) {
super.onAmbientModeChanged(inAmbientMode);
if (mAmbient != inAmbientMode) {
mAmbient = inAmbientMode;
if (mLowBitAmbient) {
mTextPaint.setAntiAlias(!inAmbientMode);
}
invalidate();
}
// Whether the timer should be running depends on whether we're visible (as well as
// whether we're in ambient mode), so we may need to start or stop the timer.
updateTimer();
}
/**
* Captures tap event (and tap type) and toggles the background color if the user finishes
* a tap.
*/
#Override
public void onTapCommand(int tapType, int x, int y, long eventTime) {
Resources resources = ternary.this.getResources();
switch (tapType) {
case TAP_TYPE_TOUCH:
// The user has started touching the screen.
break;
case TAP_TYPE_TOUCH_CANCEL:
// The user has started a different gesture or otherwise cancelled the tap.
break;
case TAP_TYPE_TAP:
// The user has completed the tap gesture.
mTapCount++;
mBackgroundPaint.setColor(resources.getColor(mTapCount % 2 == 0 ?
R.color.background : R.color.background2));
break;
}
invalidate();
}
#Override
public void onDraw(Canvas canvas, Rect bounds) {
// Greebo counter
// x += 1;
// seconds handling
wasSecs = yesSecs;
yesSecs = allowSecs && !isInAmbientMode();
// for clearing seconds
if (!yesSecs && wasSecs) { sks = ""; }
// Draw at mid second
if (c == 0 && yesSecs) {
drawNow = true;
} else {
c = 0;
// mid minute
if (mTime.second == 30 || isInAmbientMode()) {
drawNow = true;
} else {
// mid hour
if (mTime.second == 0) {
if (mTime.minute == 30) {
drawNow = true;
} else {
// mid night
if (mTime.minute == 0) {
if (mTime.hour == 0) {
drawNow = true;
}
}
}
}
}
}
if (drawNow) {
drawNow = false;
mTime.setToNow();
millis = System.currentTimeMillis() % 1000;
// mid seconds
if (yesSecs) { if (millis > 499) { c = 1; } }
tim = (float)((mTime.minute * 60 + mTime.second) * 1000 + millis)/ 3600000;
// hours past noon
tim += mTime.hour - 12;
// find hrs 9s, 3s, 1s.
openGate = false;
if (yesFirstRun || mTime.minute == 30){ openGate = true; }
else { openGate = mTime.second == 0 && mTime.minute == 0 && mTime.hour == 0;}
if (openGate) {
hrs = "";
hourInt = 0;
// i is for item.
for (int i : trits3) {
if (tim > ((float) i / 2)) {
tim -= i;
hourInt -= i;
hrs = hrs + "1";
} else {
if (tim < ((float) i / -2)) {
tim += i;
hourInt += i;
hrs = hrs + "¬";
} else {
hrs = hrs + "0";
}
}
// add space
if (i > 1) {hrs += " "; }
}
} else { tim += hourInt; }
// minutes 27s, 9s, 3s, 1s
openGate = false;
if (yesFirstRun || mTime.second == 30 || isInAmbientMode()) {openGate = true; }
else { openGate = mTime.second == 0 && (mTime.minute == 30
|| (mTime.minute == 0 && mTime.hour == 0));}
if (openGate) {
mns = "";
tim *= 60;
minInt = 0;
// i is for item.
for (int i : trits4) {
if (tim > ((float) i / 2)) {
tim -= i;
if (yesSecs) {minInt -= i;}
mns = mns + "1";
} else {
if (tim < ((float) i / -2)) {
tim += i;
if (yesSecs) {minInt += i;}
mns = mns + "¬";
} else {
mns = mns + "0";
}
}
// add space
if (i > 1) {mns += " "; }
}
} else { if (yesSecs) { tim += minInt; tim *= 60; } }
// seconds 27s, 9s, 3s, 1s
if (yesSecs) {
sks = "";
tim *= 60;
for (int i : trits4) {
if (tim > ((float) i / 2)) {
tim -= i;
sks = sks + "1";
} else {
if (tim < ((float) i / -2)) {
tim += i;
sks = sks + "¬";
} else {
sks = sks + "0";
}
}
// add space
if (i > 1) {sks += " "; }
}
}
}
// Draw the background.
if (isInAmbientMode()) {
canvas.drawColor(Color.BLACK);
} else {
canvas.drawRect(0, 0, bounds.width(), bounds.height(), mBackgroundPaint);
}
// draw hours
canvas.drawText(hrs, hrsIndent, mYOffset - ySpacer, mTextPaint);
// draw minutes
canvas.drawText(mns, mXOffset, mYOffset, mTextPaint);
// draw or clear seconds
if (yesSecs || wasSecs) {canvas.drawText(sks, mXOffset, mYOffset + ySpacer , mTextPaint);}
// show count and millis for greebo reduction.
// canvas.drawText(String.format("%1$03d,%2$02d,%3$d", x % 1000, millis / 10, 0), mXOffset, mYOffset + 100, mTextPaint);
//canvas.drawText(String.format("%$02d:%2$02d:%3$02d", mTime.hour, mTime.minute,
// mTime.second), mXOffset, mYOffset + 100, mTextPaint);
}
/**
* Starts the {#link #mUpdateTimeHandler} timer if it should be running and isn't currently
* or stops it if it shouldn't be running but currently is.
*/
private void updateTimer() {
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
if (shouldTimerBeRunning()) {
mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
}
}
/**
* Returns whether the {#link #mUpdateTimeHandler} timer should be running. The timer should
* only run when we're visible and in interactive mode.
*/
private boolean shouldTimerBeRunning() {
return isVisible() && !isInAmbientMode();
}
/**
* Handle updating the time periodically in interactive mode.
*/
private void handleUpdateTimeMessage() {
invalidate();
if (shouldTimerBeRunning()) {
long timeMs = System.currentTimeMillis();
long delayMs = INTERACTIVE_UPDATE_RATE_MS
- (timeMs % INTERACTIVE_UPDATE_RATE_MS);
mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
}
}
}
}
The "onCreate()" method seems to correlate with:
public class ternary extends CanvasWatchFaceService
Your declaration should be as follows:
public class ternary extends CanvasWatchFaceService {
private AlarmManager mAmbientStateAlarmManager;
private PendingIntent mAmbientStatePendingIntent;
And be sure to import android.app.AlarmManager and android.app.PendingIntent
.

Java midi volume control won't work

I've been trying to get midi volume control to work in a MidiPlayer class for a very long time now. I've searched for examples for accomplishing this here on stackoverflow and all over the Internet, but nothing I try ever seems to work. The volume stays the same! It doesn't change like I want it to.
I'm running Java 1.6.0_32 on Windows 7 professional.
Here! Have an SSCCE:
import java.io.*;
import javax.sound.midi.*;
import java.net.URL;
public class MidiSSCCE {
public static void main(String[] args)
{
// creates the midi player and sets up its sequencer and synthesizer.
MidiPlayer midiP = new MidiPlayer();
double volume = 1.0;
// loads a midi from a url into the sequencer, but doesn't start playing it yet.
midiP.load("http://www.vgmusic.com/music/computer/microsoft/windows/touhou_6_stage3_boss.mid",true);
// set the midi to loop indefinitely.
midiP.loop(-1);
// start playing the midi.
midiP.play(true);
// while loop changes the volume of the midi while it is playing.
while(true) {
midiP.setVolume(volume);
try { Thread.sleep(300); } catch(Exception e) {}
volume -= 0.1;
if(volume < 0) volume += 1.0;
}
}
}
/**
* MidiPlayer
* author: Stephen Lindberg
* Last modified: Oct 14, 2011
*
* A class that allows midi files to be loaded and played.
**/
class MidiPlayer {
private Sequence seq;
private Sequencer seqr;
private Synthesizer synth;
private Receiver receiver;
private File midiFile;
private String midiID;
private boolean loaded;
private boolean usingHardwareSoundbank;
// CONSTRUCTORS
public MidiPlayer() {
loaded = false;
try {
// obtain the sequencer and synthesizer.
seqr = MidiSystem.getSequencer();
synth = MidiSystem.getSynthesizer();
// print the user's midi device info
System.out.println("Setting up Midi Player...");
System.out.println("MidiDeviceInfo: ");
for(MidiDevice.Info info : MidiSystem.getMidiDeviceInfo()) {
System.out.println("\t" + info.getName() + ": " +info.getDescription());
}
System.out.println();
// obtain the soundbank and receiver.
Soundbank soundbank = synth.getDefaultSoundbank();
if(soundbank == null) {
receiver = MidiSystem.getReceiver();
usingHardwareSoundbank = true;
System.out.println("using hardware soundbank");
}
else {
synth.loadAllInstruments(soundbank);
receiver = synth.getReceiver();
usingHardwareSoundbank = false;
System.out.println("using default software soundbank:" + soundbank);
}
seqr.getTransmitter().setReceiver(receiver);
}
catch(Exception e) {
System.out.println("MIDI error: I just don't know what went wrong! 6_9");
}
}
// DATA METHODS
/**
* load(String fileName)
* loads a midi file into this MidiPlayer.
* Preconditions: fileName is the name of the midi file to be loaded.
* Postconditions: fileName is loaded and is ready to be played.
**/
public void load(String fileName, boolean isOnline) {
this.unload();
try {
URL midiURL;
if(isOnline) midiURL = new URL(fileName);
else midiURL = getClass().getClassLoader().getResource(fileName);
seq = MidiSystem.getSequence(midiURL);
seqr.open();
synth.open();
// load our sequence into the sequencer.
seqr.setSequence(seq);
loaded = true;
}
catch(IOException ioe) {
System.out.println("MIDI error: Problem occured while reading " + midiFile.getName() + ".");
}
catch(InvalidMidiDataException imde) {
System.out.println("MIDI error: " + midiFile.getName() + " is not a valid MIDI file or is unreadable.");
}
catch(Exception e) {
System.out.println("MIDI error: I just don't know what went wrong! 6_9");
}
}
/**
* unload()
* Unloads the current midi from the MidiPlayer and releases its resources from memory.
**/
public void unload() {
this.stop();
seqr.close();
synth.close();
midiFile = null;
loaded = false;
}
// OTHER METHODS
/**
* play(boolean reset)
* plays the currently loaded midi.
* Preconditions: reset tells our midi whether or nor to begin playing from the start of the midi file's current loop start point.
* Postconditions: If reset is true, then the loaded midi begins playing from its loop start point (default 0).
* If reset is false, then the loaded midi resumes playing from its current position.
**/
public void play(boolean reset) {
if(reset) seqr.setTickPosition(seqr.getLoopStartPoint());
seqr.start();
}
/**
* stop()
* Pauses the current midi if it was playing.
**/
public void stop() {
if(seqr.isOpen()) seqr.stop();
}
/**
* isRunning()
* Returns true if the current midi is playing. Returns false otherwise.
**/
public boolean isRunning() {
return seqr.isRunning();
}
/**
* loop(int times)
* Sets the current midi to loop from start to finish a specific number of times.
* Preconditions: times is the number of times we want our midi to loop.
* Postconditions: The current midi is set to loop times times.
* If times = -1, the current midi will be set to loop infinitely.
**/
public void loop(int times)
{
loop(times,0,-1);
}
/**
* loop(int times)
* Sets the current midi to loop from a specified start point to a specified end point a specific number of times.
* Preconditions: times is the number of times we want our midi to loop.
* start is our loop's start point in ticks.
* end is our loop's end point in ticks.
* Postconditions: The current midi is set to loop from tick start to tick end times times.
* If times = -1, the current midi will be set to loop infinitely.
**/
public void loop(int times, long start, long end) {
if(start < 0) start = 0;
if(end > seqr.getSequence().getTickLength() || end <= 0) end = seqr.getSequence().getTickLength();
if(start >= end && end != -1) start = end-1;
seqr.setLoopStartPoint(start);
seqr.setLoopEndPoint(end);
if(times == -1) seqr.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
else seqr.setLoopCount(times);
}
public void setVolume(double vol) {
System.out.println("Midi volume change request: " + vol);
try {
if(usingHardwareSoundbank) {
ShortMessage volumeMessage = new ShortMessage();
for ( int i = 0; i < 16; i++ ) {
volumeMessage.setMessage( ShortMessage.CONTROL_CHANGE, i, 7, (int)(vol*127) );
receiver.send( volumeMessage, -1 );
}
}
else {
MidiChannel[] channels = synth.getChannels();
for( int c = 0; c < channels.length; c++ ) {
if(channels[c] != null) channels[c].controlChange( 7, (int)( vol*127) );
}
}
}
catch ( Exception e ) {
e.printStackTrace();
}
}
}
I've tried examples at the following sources with no success:
http://www.java2s.com/Code/Java/Development-Class/SettingtheVolumeofPlayingMidiAudio.htm
How to controll the MIDI channel's volume
https://forums.oracle.com/forums/thread.jspa?messageID=5389030
MIDI Song with CC
http://www.codezealot.org/archives/27
http://www.exampledepot.com/egs/javax.sound.midi/Volume.html
I'm struggling with these very issues on controlling sound and i've found a code to change volume that works. Unfurtunatly i was unable to understand it, but here is something in your code that is differente from the one i've seen. Maybe it can helps you.
Try change the line
seqr = MidiSystem.getSequencer();
for
seqr = MidiSystem.getSequencer(false);
Maybe it helps you, I believe that using the "false" the sequencer will connect to the receiver, and not to the synthesizer, then when you send the message to the receiver to set volume it will work.
public void setVolume(double vol) {
System.out.println("Midi volume change request: " + vol);
try {
if(usingHardwareSoundbank) {
ShortMessage volumeMessage = new ShortMessage();
for ( int i = 0; i < 16; i++ ) {
volumeMessage.setMessage( ShortMessage.CONTROL_CHANGE, i, 7, (int)(vol*127) );
receiver.send( volumeMessage, -1 );
}
}
else {
MidiChannel[] channels = synth.getChannels();
for( int c = 0; c < channels.length; c++ ) {
if(channels[c] != null) channels[c].controlChange( 7, (int)( vol*127) );
}
}
// Very important!
seqr.setSequence(seq);
}
catch ( Exception e ) {
e.printStackTrace();
}
}

Circular ShortBuffer for Audio in Java

I'm implementing an audio track class and I'm in need of a good circular buffer implementation. I'm using shorts for my audio samples, so I would prefer to use a ShortBuffer class for the actual buffer. This track will need to be thread-safe but I can guarantee that only one thread will read and another will write on the track.
My current implementation looks like this (it doesn't handle wrapping).
public class Track {
//sample rate 44100, 2 channels with room for 4 seconds
private volatile ShortBuffer buffer = ShortBuffer.allocate((44100 * 2) * 4);
//keep count of the samples in the buffer
private AtomicInteger count = new AtomicInteger(0);
private ReentrantLock lock = new ReentrantLock(true);
private int readPosition = 0;
public int getSampleCount() {
int i = count.get();
return i > 0 ? i / 2 : 0;
}
public short[] getSamples(int sampleCount) {
short[] samples = new short[sampleCount];
try {
lock.tryLock(10, TimeUnit.MILLISECONDS);
int writePosition = buffer.position();
buffer.position(readPosition);
buffer.get(samples);
//set new read position
readPosition = buffer.position();
// set back to write position
buffer.position(writePosition);
count.addAndGet(-sampleCount);
} catch (InterruptedException e) {
System.err.println("Exception getting samples" + e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return samples;
}
public void pushSamples(short[] samples) {
try {
lock.tryLock(10, TimeUnit.MILLISECONDS);
buffer.put(samples);
count.addAndGet(samples.length);
} catch (InterruptedException e) {
System.err.println("Exception getting samples" + e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
Here's the solution that I have come up with http://pastebin.com/2St01Wzf I decided it was easier to use a head and tail property with a short array, instead of just the read position with a ShortBuffer. I also took an idea from the Java collections classes to detect when the buffer is full. Here is the source, just in case the pastebin disappears:
public class Track {
private static Logger log = LoggerFactory.getLogger(Track.class);
private final long id = System.nanoTime();
// number of channels
private int channelCount;
// maximum seconds to buffer
private int bufferedSeconds = 5;
private AtomicInteger count = new AtomicInteger(0);
private ReentrantLock lock;
private volatile short[] buffer;
private int capacity = 0;
private int head = 0;
private int tail = 0;
public Track(int samplingRate, int channelCount) {
// set the number of channels
this.channelCount = channelCount;
// size the buffer
capacity = (samplingRate * channelCount) * bufferedSeconds;
buffer = new short[capacity];
// use a "fair" lock
lock = new ReentrantLock(true);
}
/**
* Returns the number of samples currently in the buffer.
*
* #return
*/
public int getSamplesCount() {
int i = count.get();
return i > 0 ? i / channelCount : 0;
}
/**
* Removes and returns the next sample in the buffer.
*
* #return single sample or null if a buffer underflow occurs
*/
public Short remove() {
Short sample = null;
if (count.get() > 0) {
// decrement sample counter
count.addAndGet(-1);
// reposition the head
head = (head + 1) % capacity;
// get the sample at the head
sample = buffer[head];
} else {
log.debug("Buffer underflow");
}
return sample;
}
/**
* Adds a sample to the buffer.
*
* #param sample
* #return true if added successfully and false otherwise
*/
public boolean add(short sample) {
boolean result = false;
if ((count.get() + 1) < capacity) {
// increment sample counter
count.addAndGet(1);
// reposition the tail
tail = (tail + 1) % capacity;
// add the sample to the tail
buffer[tail] = sample;
// added!
result = true;
} else {
log.debug("Buffer overflow");
}
return result;
}
/**
* Offers the samples for addition to the buffer, if there is enough capacity to
* contain them they will be added.
*
* #param samples
* #return true if the samples can be added and false otherwise
*/
public boolean offer(short[] samples) {
boolean result = false;
if ((count.get() + samples.length) <= capacity) {
pushSamples(samples);
result = true;
}
return result;
}
/**
* Adds an array of samples to the buffer.
*
* #param samples
*/
public void pushSamples(short[] samples) {
log.trace("[{}] pushSamples - count: {}", id, samples.length);
try {
lock.tryLock(10, TimeUnit.MILLISECONDS);
for (short sample : samples) {
log.trace("Position at write: {}", tail);
if (!add(sample)) {
log.warn("Sample could not be added");
break;
}
}
} catch (InterruptedException e) {
log.warn("Exception getting samples", e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* Returns a single from the buffer.
*
* #return
*/
public Short popSample(int channel) {
log.trace("[{}] popSample - channel: {}", id, channel);
Short sample = null;
if (channel < channelCount) {
log.trace("Position at read: {}", head);
try {
lock.tryLock(10, TimeUnit.MILLISECONDS);
sample = remove();
} catch (InterruptedException e) {
log.warn("Exception getting sample", e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
return sample;
}
}

Categories