/**
*
* @author greg (at) myrobotlab.org
*
* This file is part of MyRobotLab (http://myrobotlab.org).
*
* MyRobotLab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version (subject to the "Classpath" exception
* as provided in the LICENSE.txt file that accompanied this code).
*
* MyRobotLab is distributed in the hope that it will be useful or fun,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* All libraries in thirdParty bundle are subject to their own license
* requirements - please refer to http://myrobotlab.org/libraries for
* details.
*
* Enjoy !
*
* */
package org.myrobotlab.service;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.myrobotlab.audio.AudioProcessor;
import org.myrobotlab.framework.Service;
import org.myrobotlab.framework.ServiceType;
import org.myrobotlab.logging.Level;
import org.myrobotlab.logging.LoggerFactory;
import org.myrobotlab.logging.Logging;
import org.myrobotlab.logging.LoggingFactory;
import org.myrobotlab.service.data.AudioData;
import org.slf4j.Logger;
/**
*
* AudioFile - This service can be used to play an audio file such as an mp3.
*
*/
public class AudioFile extends Service {
static final long serialVersionUID = 1L;
static Logger log = LoggerFactory.getLogger(AudioFile.class);
static public final String DEFAULT_TRACK = "default";
// FIXME -
// http://www.javalobby.org/java/forums/t18465.html
// http://sourcecodebrowser.com/libjlayer-java/1.0/classjavazoom_1_1jl_1_1player_1_1_audio_device_base__coll__graph.png
// dataLine.getControls() returns this Master Gain with current value: 0.0 dB
// (range: -80.0 - 6.0206)
// Mute Control with current value: False Balance with current value: 0.0
// (range: -1.0 - 1.0)
// Pan with current value: 0.0 (range: -1.0 - 1.0) Ive been messing with my
// code in the meantime and realized that the Master
// Gains values IS actually changed but I can`t hear any cahnges in the song
// http://alvinalexander.com/java/java-audio-example-java-au-play-sound - so
// much more simple
// http://www.programcreek.com/java-api-examples/index.php?api=javax.sound.sampled.FloatControl
// http://stackoverflow.com/questions/198679/convert-audio-stream-to-wav-byte-array-in-java-without-temp-file
// TODO - utilize
// http://docs.oracle.com/javase/7/docs/api/javax/sound/sampled/Clip.html
public static final String MODE_BLOCKING = "blocking";
public static final String MODE_QUEUED = "queued";
static String globalFileCacheDir = "audioFile";
public static final String journalFilename = "journal.txt";
// Map<String, BlockingQueue<AudioData>> tracks = new HashMap<String,
// BlockingQueue<AudioData>>();// new
String currentTrack = DEFAULT_TRACK;
transient Map<String, AudioProcessor> processors = new HashMap<String, AudioProcessor>();
// Map<String, Object> locks = new HashMap<String, Object>();
Map<String, Object> waitForLocks = new HashMap<String, Object>();
public AudioFile(String n) {
super(n);
}
public void track(String trackName) {
currentTrack = trackName;
if (!processors.containsKey(trackName)) {
AudioProcessor processor = new AudioProcessor(this, trackName);
processors.put(trackName, processor);
processor.start();
}
}
// TODO test with jar://resource/AudioFile/tick.mp3 & https://host/mp3 :
// localfile
//
public AudioData play(String filename) {
if (filename == null) {
log.warn("asked to play a null filename! error");
return null;
}
File f = new File(filename);
if (!f.exists()) {
log.warn("Tried to play file " + f.getAbsolutePath() + " but it was not found.");
return null;
}
// use File interface such that filename is preserved
// but regardless of location (e.g. url, local, resource)
// or type (mp3 wav) a stream is opened and the
// pair is put on a queue to be played
// playFile("audioFile/" + filename + ".mp3", false);
return play(new AudioData(filename));
}
public AudioData play(AudioData data) {
// use File interface such that filename is preserved
// but regardless of location (e.g. url, local, resource)
// or type (mp3 wav) a stream is opened and the
// pair is put on a queue to be played
// make sure we are on
// the currentTrack and its
// created if necessary
track(currentTrack);
if (MODE_QUEUED.equals(data.mode)) {
// stick it on top of queue and let our default player play it
return processors.get(currentTrack).add(data);
} else if (MODE_BLOCKING.equals(data.mode)) {
return processors.get(currentTrack).play(data);
}
return data;
}
public void playBlocking(String filename) {
playFile(filename, true);
}
public void pause() {
processors.get(currentTrack).pause = true;
}
public void resume() {
processors.get(currentTrack).pause = false;
Object lock = processors.get(currentTrack).getLock();
synchronized (lock) {
lock.notify();
}
}
public void playFile(String filename) {
playFile(filename, false);
}
public void playFile(String filename, Boolean isBlocking) {
if (filename == null) {
log.warn("Asked to play a null file, sorry can't do that");
return;
}
File f = new File(filename);
if (!f.exists()) {
log.warn("File not found to play back " + f.getAbsolutePath());
return;
}
AudioData data = new AudioData(filename);
if (isBlocking) {
data.mode = AudioFile.MODE_BLOCKING;
} else {
data.mode = AudioFile.MODE_QUEUED;
}
play(data);
}
public void playFileBlocking(String filename) {
playFile(filename, true);
}
public void playResource(String filename) {
playResource(filename, false);
};
public void playResource(String filename, Boolean isBlocking) {
// TODO: what/who uses this? should we use the class loader to
// playFile(filename, isBlocking, true);
log.warn("Audio File playResource not implemented yet.");
}
public void silence() {
// stop all tracks
for (Map.Entry<String, AudioProcessor> entry : processors.entrySet()) {
String key = entry.getKey();
processors.get(key).isPlaying = false;
processors.get(key).pause = true;
// do what you have to do here
// In your case, an other loop.
}
}
/**
* Specify the volume for playback on the audio file value 0.0 = off 1.0 =
* normal volume. (values greater than 1.0 may distort the original signal)
*
* @param volume
*/
public void setVolume(float volume) {
processors.get(currentTrack).setVolume(volume);
}
public void setVolume(double volume) {
processors.get(currentTrack).setVolume((float) volume);
}
public float getVolume() {
return processors.get(currentTrack).getVolume();
}
public boolean cacheContains(String filename) {
File file = new File(globalFileCacheDir + File.separator + filename);
return file.exists();
}
public AudioData playCachedFile(String filename) {
return play(globalFileCacheDir + File.separator + filename);
}
public void cache(String filename, byte[] data, String toSpeak) throws IOException {
File file = new File(globalFileCacheDir + File.separator + filename);
File parentDir = new File(file.getParent());
if (!parentDir.exists()) {
parentDir.mkdirs();
}
FileOutputStream fos = new FileOutputStream(globalFileCacheDir + File.separator + filename);
fos.write(data);
fos.close();
// Now append the journal entry to the journal.txt file
FileWriter journal = new FileWriter(globalFileCacheDir + File.separator + journalFilename, true);
journal.append(filename + "," + toSpeak + "\r\n");
journal.close();
}
public static String getGlobalFileCacheDir() {
return globalFileCacheDir;
}
public String getTrack() {
return currentTrack;
}
public void setVolume(float volume, String track) {
track(track);
setVolume(volume);
}
// does this work ?
public AudioData repeat(String filename, String track) {
track(track);
repeat();
return play(filename);
}
/*
* TODO IMPLEMENT ??? private AudioProcessor getOrCreateTrack(String track){
*
* }
*
*/
// vs waitFor(String track, String waitingOn)
public void waitFor(String waitingTrack, String track, AudioData waitForMe) {
AudioProcessor processor = null;
if (!processors.containsKey(waitingTrack)) {
processor = new AudioProcessor(this, waitingTrack);
processors.put(waitingTrack, processor);
} else {
processor = processors.get(waitingTrack);
}
processors.get(waitingTrack).waitForKey = waitForMe.toString();
// processors.get(currentTrack)
/*
* synchronized (locks.get(currentTrack)) { locks.get(currentTrack).wait();
* }
*/
if (!processor.isRunning()) {
processor.start();
}
}
public Object getWaitForLock(String key) {
if (waitForLocks.containsKey(key)) {
return waitForLocks.get(key);
}
return null;
}
public void repeat() {
// TODO Auto-generated method stub
processors.get(currentTrack).repeat = true;
}
public void stop() {
// dump the current song
processors.get(currentTrack).isPlaying = false;
// pause the next one if queued
processors.get(currentTrack).pause = true;
}
/*
* public AudioData getNextAudioData(String track) throws InterruptedException
* { return tracks.get(track).take(); }
*/
/*
* public Object getLock(String track) { return locks.get(track); }
*/
public List<Object> getLocksWaitingFor(String queueName) {
// TODO Auto-generated method stub
return null;
}
public static void main(String[] args) {
LoggingFactory.init(Level.DEBUG);
try {
// FIXME
// TO - TEST
// file types mp3 wav
// file locations http resource jar! file cache !!!
// basic controls - playMulti, playQueue, playBlocking
// FIXME - setting volume on a non-played track null pointer
// FIXME - DNA make single AudioFile
// AudioFile audio = (AudioFile) Runtime.createAndStart("audio",
// "AudioFile");
// MarySpeech mary = (MarySpeech) Runtime.start("mary", "MarySpeech");
log.info(AcapelaSpeech.getDNA().toString());
AcapelaSpeech robot1 = (AcapelaSpeech) Runtime.start("robot1", "AcapelaSpeech");
// AcapelaSpeech robot1 = (AcapelaSpeech) Runtime.createAndStart("robot1",
// "AcapelaSpeech");
AudioFile audio = robot1.getAudioFile();
log.info(audio.getTrack());
// audio.track("sound effects");
audio.track("explosion");
audio.play("explosion.mp3");
/*
* mary.speak("warning warning. danger will robinson danger"); mary.speak(
* "yes i might not sound quite as sexy as the other speech services but i am open source"
* ); mary.speak("and they can do things like this");
*/
// FIXME all use the same single AudioFile
// basic dialog - they are all on the same track so stacked up and
// interleaved
// audio.track(DEFAULT_TRACK); // FIXME - Acapela - needs to create robot1
// Track !!
audio.track(); // FIXME - Acapela - needs to create robot1 Track !!
robot1.speak("Rhona", "sir.. I believe we are under attack from inter-bots");
robot1.speak("Tyler", "big deal, its just a bunch of inter-bots.. i really dont think it is a problem... I mean we are");
robot1.speak("Rhona", "what? how can you just sit there on your metal butt and do nothing");
robot1.speak("Tyler", "calm down - please just calm down");
robot1.speak("Rhona", "fine, some one needs to take care of this. Im going to fight them off with my bare hands");
AudioData alarm = robot1.speak("Rhona", "i better start the alarm and warn the rest of the crew");
robot1.speakBlocking("I am testing blocking on speech");
/// audio.repeat("flybye.mp3","flybye");
// FIXME - implement - audio.waitFor(alarm);
// audio.waitFor("alert", "default");
// audio.waitForAll("alert");
audio.waitFor("alert", "default", alarm);
audio.repeat("alert.mp3", "alert");
audio.track("default");
// FIXME - implement audio.pause(1000); - also implement a "queued"
// pause(1000);
robot1.speak("Tyler", "ahh noooooo, you didn't really need to do that did you. you know how the klaxons give me a headache");
robot1.speak("Rhona", "get off your butt and doooo something");
robot1.speak("i am going aft get a battle axe in the weapons locker");
robot1.speak("by the time i get back, you had better be ready");
robot1.speak("Tyler", "first thing i'm going to do is turn this thing down");
audio.setVolume(0.20f, "alert"); // <-- name the robot's audio file
audio.track("default");
robot1.speak("ahhh.. that's better - how can anyone think with that thing");
audio.play("walking.mp3");
robot1.speak("Rhona", "i mean it. tyler");
audio.play("door.mp3");
robot1.speak("Tyler", "hello honey, what is your name");
// mary.speak("hello my name is mary");
robot1.speak("hi i'm Tyler - i think you sound a little like a robot");
// mary.speak("yes, but i am open source");
robot1.speak("can you dance like a robot too?");
// mary.speak("i will try");
// audio.setVolume(0.50f);
robot1.speak("all right now she's gone its time to get funky");
audio.play("scruff.mp3");
robot1.speak("i need some snacks - i wonder if there is any left over chinese in the fridge");
// turn the klaxons down - turn the music up
// another woman
// establishing a priority queue
audio.track("priority");
audio.play("alert.mp3");
audio.setVolume(0.50f);
audio.setVolume(0.20f);
audio.play("nature.wav");
audio.pause();
audio.resume();
audio.pause();
audio.resume();
audio.silence();
// audio.fadeOut() // fadeOut is fadeOutAll
// audio.fadeOutAll() fadeOut(track) - time option too
// audio.fadeIn() //
// audio.fadeInAll()
// audio.setAllVolume();
// af.playFile("C:\\dev\\workspace.kmw\\myrobotlab\\test.mp3",
// false, false);
audio.stop();
audio.resume();
boolean test = false;
if (test) {
audio.silence();
Joystick joystick = (Joystick) Runtime.createAndStart("joy", "Joystick");
Runtime.createAndStart("python", "Python");
AudioFile player = new AudioFile("player");
// player.playFile(filename, true);
player.startService();
Runtime.createAndStart("gui", "GUIService");
joystick.setController(2);
joystick.broadcastState();
// BasicController control = (BasicController) player;
player.playFile("C:\\Users\\grperry\\Downloads\\soapBox\\thump.mp3");
player.playFile("C:\\Users\\grperry\\Downloads\\soapBox\\thump.mp3");
player.playFile("C:\\Users\\grperry\\Downloads\\soapBox\\thump.mp3");
player.playFile("C:\\Users\\grperry\\Downloads\\soapBox\\start.mp3");
player.playFile("C:\\Users\\grperry\\Downloads\\soapBox\\radio.chatter.4.mp3");
player.silence();
// player.playResource("Clock/tick.mp3");
player.playResource("/resource/Clock/tick.mp3");
player.playResource("/resource/Clock/tick.mp3");
player.playResource("/resource/Clock/tick.mp3");
player.playResource("/resource/Clock/tick.mp3");
player.playResource("/resource/Clock/tick.mp3");
player.playResource("/resource/Clock/tick.mp3");
player.playResource("/resource/Clock/tick.mp3");
}
} catch (Exception e) {
Logging.logError(e);
}
// player.playBlockingWavFile("I am ready.wav");
// player.play("hello my name is audery");
// player.playWAV("hello my name is momo");
}
public void track() {
track(DEFAULT_TRACK);
}
public String finishedPlaying(String utterance) {
// TODO: maybe wire though the utterance?
log.info("Finished playing called");
return utterance;
}
public AudioData publishAudioStart(AudioData data) {
return data;
}
public AudioData publishAudioEnd(AudioData data) {
log.info("Audio File publishAudioEnd");
return data;
}
/**
* This static method returns all the details of the class without it having
* to be constructed. It has description, categories, dependencies, and peer
* definitions.
*
* @return ServiceType - returns all the data
*
*/
static public ServiceType getMetaData() {
ServiceType meta = new ServiceType(AudioFile.class.getCanonicalName());
meta.addDescription("Plays back audio file. Can block or multi-thread play");
meta.addCategory("sound");
meta.addDependency("javazoom.spi", "1.9.5");
meta.addDependency("javazoom.jl.player", "1.0.1");
meta.addDependency("org.tritonus.share.sampled.floatsamplebuffer", "0.3.6");
return meta;
}
}