diff --git a/app/alarm/audio-annunciator/.classpath b/app/alarm/audio-annunciator/.classpath new file mode 100644 index 0000000000..948256b10b --- /dev/null +++ b/app/alarm/audio-annunciator/.classpath @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/alarm/audio-annunciator/build.xml b/app/alarm/audio-annunciator/build.xml new file mode 100644 index 0000000000..019188b2fa --- /dev/null +++ b/app/alarm/audio-annunciator/build.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/alarm/audio-annunciator/pom.xml b/app/alarm/audio-annunciator/pom.xml new file mode 100644 index 0000000000..085313f76a --- /dev/null +++ b/app/alarm/audio-annunciator/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + org.phoebus + app-alarm + 4.7.4-SNAPSHOT + + app-alarm-audio-annunciator + + + 17 + 17 + + + + + org.phoebus + app-alarm-ui + 4.7.4-SNAPSHOT + + + org.openjfx + javafx-media + ${openjfx.version} + + + + \ No newline at end of file diff --git a/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java new file mode 100644 index 0000000000..7d9600b967 --- /dev/null +++ b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/AudioAnnunciator.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2018 Oak Ridge National Laboratory. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.phoebus.applications.alarm.audio.annunciator; + +import javafx.scene.media.AudioClip; +import javafx.scene.media.Media; +import javafx.scene.media.MediaPlayer; +import javafx.util.Duration; +import org.phoebus.applications.alarm.ui.annunciator.Annunciator; +import org.phoebus.applications.alarm.ui.annunciator.AnnunciatorMessage; + +import java.util.List; + +/** + * Annunciator class. Uses Audio files to annunciate passed messages. + * + * @author Kunal Shroff + */ +@SuppressWarnings("nls") +public class AudioAnnunciator implements Annunciator { + private final MediaPlayer alarmSound; + private final MediaPlayer minorAlarmSound; + private final MediaPlayer majorAlarmSound; + private final MediaPlayer invalidAlarmSound; + private final MediaPlayer undefinedAlarmSound; + + /** + * Constructor + */ + public AudioAnnunciator() { + alarmSound = new MediaPlayer(new Media(Preferences.alarm_sound_url)); + minorAlarmSound = new MediaPlayer(new Media(Preferences.minor_alarm_sound_url)); + majorAlarmSound = new MediaPlayer(new Media(Preferences.major_alarm_sound_url)); + invalidAlarmSound = new MediaPlayer(new Media(Preferences.invalid_alarm_sound_url)); + undefinedAlarmSound = new MediaPlayer(new Media(Preferences.undefined_alarm_sound_url)); + // configure the media players for the different alarm sounds + List.of(alarmSound, minorAlarmSound, majorAlarmSound, invalidAlarmSound, undefinedAlarmSound) + .forEach(sound -> { + sound.setStopTime(Duration.seconds(Preferences.max_alarm_duration)); + sound.setVolume(Preferences.volume); + }); + } + + /** + * Annunciate the message. + * + * @param message Message text + */ + @Override + public void speak(final AnnunciatorMessage message) { + switch (message.severity) { + case MINOR -> speakAlone(minorAlarmSound); + case MAJOR -> speakAlone(majorAlarmSound); + case INVALID -> speakAlone(invalidAlarmSound); + case UNDEFINED -> speakAlone(undefinedAlarmSound); + default -> speakAlone(alarmSound); + } + } + + synchronized private void speakAlone(MediaPlayer alarm) { + List.of(alarmSound, minorAlarmSound, majorAlarmSound, invalidAlarmSound, undefinedAlarmSound) + .forEach(sound -> { + sound.stop(); + }); + alarm.play(); + } + + /** + * Deallocates the voice. + */ + @Override + public void shutdown() { + List.of(alarmSound, minorAlarmSound, majorAlarmSound, invalidAlarmSound, undefinedAlarmSound) + .forEach(sound -> { + sound.stop(); + sound.dispose(); + }); + } +} diff --git a/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/Preferences.java b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/Preferences.java new file mode 100644 index 0000000000..cbbf2bf89e --- /dev/null +++ b/app/alarm/audio-annunciator/src/main/java/org/phoebus/applications/alarm/audio/annunciator/Preferences.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2010-2022 Oak Ridge National Laboratory. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ +package org.phoebus.applications.alarm.audio.annunciator; + + +import org.phoebus.framework.preferences.AnnotatedPreferences; +import org.phoebus.framework.preferences.Preference; +import org.phoebus.framework.preferences.PreferencesReader; + +/** + * Helper for reading preference settings + * + * @author Kunal Shroff + */ +@SuppressWarnings("nls") +public class Preferences { + + /** + * Setting + */ + @Preference + public static String alarm_sound_url; + @Preference + public static String minor_alarm_sound_url; + @Preference + public static String major_alarm_sound_url; + @Preference + public static String invalid_alarm_sound_url; + @Preference + public static String undefined_alarm_sound_url; + @Preference + public static int volume; + @Preference + public static int max_alarm_duration; + + static { + final PreferencesReader prefs = AnnotatedPreferences.initialize(AudioAnnunciator.class, Preferences.class, "/audio_annunciator_preferences.properties"); + alarm_sound_url = useLocalResourceIfUnspecified(alarm_sound_url); + minor_alarm_sound_url = useLocalResourceIfUnspecified(minor_alarm_sound_url); + major_alarm_sound_url = useLocalResourceIfUnspecified(major_alarm_sound_url); + invalid_alarm_sound_url = useLocalResourceIfUnspecified(invalid_alarm_sound_url); + undefined_alarm_sound_url = useLocalResourceIfUnspecified(undefined_alarm_sound_url); + } + + private static String useLocalResourceIfUnspecified(String alarmResource) { + if (alarmResource == null || alarmResource.isEmpty()) { + // If only the alarm sound url is set, in a case where we want to use the same alarm sound for all severties + if (!alarm_sound_url.isEmpty()) { + return alarm_sound_url; + } else { + return Preferences.class.getResource("/sounds/mixkit-classic-alarm-995.wav").toString(); + } + } else { + return alarmResource; + } + } + +} diff --git a/app/alarm/audio-annunciator/src/main/resources/META-INF/services/org.phoebus.applications.alarm.ui.annunciator.Annunciator b/app/alarm/audio-annunciator/src/main/resources/META-INF/services/org.phoebus.applications.alarm.ui.annunciator.Annunciator new file mode 100644 index 0000000000..4377b88ff8 --- /dev/null +++ b/app/alarm/audio-annunciator/src/main/resources/META-INF/services/org.phoebus.applications.alarm.ui.annunciator.Annunciator @@ -0,0 +1 @@ +org.phoebus.applications.alarm.audio.annunciator.AudioAnnunciator diff --git a/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties b/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties new file mode 100644 index 0000000000..c8383b2611 --- /dev/null +++ b/app/alarm/audio-annunciator/src/main/resources/audio_annunciator_preferences.properties @@ -0,0 +1,23 @@ +# ---------------------------------------- +# Package org.phoebus.applications.alarm.audio.annunciator +# ---------------------------------------- + +# The audio annunciator will play the audio files specified for the associated alarm severity levels. +# Currently supported formats are AIFF and WAV files. +# examples of audio file URL's, they can be local or remote files. +# file:/C:/tmp/audio/AudioFileWithWavFormat.wav +# https://wavlist.com/wav/brass1.wav + +# default alarm sound, if we don't want severity specific sounds only setting this one preference is enough +alarm_sound_url= + +minor_alarm_sound_url= +major_alarm_sound_url= +invalid_alarm_sound_url= +undefined_alarm_sound_url= + +# audio clip volume (0-100) +volume=100 + +# max alarm Duration in seconds. +max_alarm_duration=10 \ No newline at end of file diff --git a/app/alarm/audio-annunciator/src/main/resources/sounds/mixkit-classic-alarm-995.wav b/app/alarm/audio-annunciator/src/main/resources/sounds/mixkit-classic-alarm-995.wav new file mode 100644 index 0000000000..e1d7f42d5c Binary files /dev/null and b/app/alarm/audio-annunciator/src/main/resources/sounds/mixkit-classic-alarm-995.wav differ diff --git a/app/alarm/freetts-annunciator/.classpath b/app/alarm/freetts-annunciator/.classpath new file mode 100644 index 0000000000..948256b10b --- /dev/null +++ b/app/alarm/freetts-annunciator/.classpath @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/alarm/freetts-annunciator/build.xml b/app/alarm/freetts-annunciator/build.xml new file mode 100644 index 0000000000..8f2787706f --- /dev/null +++ b/app/alarm/freetts-annunciator/build.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/alarm/freetts-annunciator/pom.xml b/app/alarm/freetts-annunciator/pom.xml new file mode 100644 index 0000000000..58b9bbd018 --- /dev/null +++ b/app/alarm/freetts-annunciator/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + org.phoebus + app-alarm + 4.7.4-SNAPSHOT + + app-alarm-freetts-annunciator + + + 17 + 17 + + + + + net.sf.sociaal + freetts + 1.2.2 + + + org.phoebus + app-alarm-ui + 4.7.4-SNAPSHOT + compile + + + + \ No newline at end of file diff --git a/app/alarm/freetts-annunciator/src/main/java/org/phoebus/applications/alarm/freetts/annunciator/FreeTTSAnnunciator.java b/app/alarm/freetts-annunciator/src/main/java/org/phoebus/applications/alarm/freetts/annunciator/FreeTTSAnnunciator.java new file mode 100644 index 0000000000..cc5d6674f8 --- /dev/null +++ b/app/alarm/freetts-annunciator/src/main/java/org/phoebus/applications/alarm/freetts/annunciator/FreeTTSAnnunciator.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2018 Oak Ridge National Laboratory. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.phoebus.applications.alarm.freetts.annunciator; + +import com.sun.speech.freetts.Voice; +import com.sun.speech.freetts.VoiceManager; +import org.phoebus.applications.alarm.ui.annunciator.Annunciator; +import org.phoebus.applications.alarm.ui.annunciator.AnnunciatorMessage; + +/** + * Annunciator class. Uses freeTTS to annunciate passed messages. + * @author Evan Smith, Kunal Shroff + */ +@SuppressWarnings("nls") +public class FreeTTSAnnunciator implements Annunciator +{ + private final VoiceManager voiceManager; + private final Voice voice; + private static final String voice_name = "kevin16"; + + /** Constructor */ + public FreeTTSAnnunciator() + { + // Define the voices directory. + System.setProperty("freetts.voices", "com.sun.speech.freetts.en.us.cmu_us_kal.KevinVoiceDirectory"); + voiceManager = VoiceManager.getInstance(); + voice = voiceManager.getVoice(voice_name); + voice.allocate(); + } + + /** + * Annunciate the message. Only returns once speaking finishes. + * @param message Message text + */ + @Override + public void speak(final AnnunciatorMessage message) + { + if (null != message) + voice.speak(message.message); + } + + /** + * Deallocates the voice. + */ + @Override + public void shutdown() + { + voice.deallocate(); + } +} diff --git a/app/alarm/freetts-annunciator/src/main/resources/META-INF/services/org.phoebus.applications.alarm.ui.annunciator.Annunciator b/app/alarm/freetts-annunciator/src/main/resources/META-INF/services/org.phoebus.applications.alarm.ui.annunciator.Annunciator new file mode 100644 index 0000000000..06c0cfc350 --- /dev/null +++ b/app/alarm/freetts-annunciator/src/main/resources/META-INF/services/org.phoebus.applications.alarm.ui.annunciator.Annunciator @@ -0,0 +1 @@ +org.phoebus.applications.alarm.freetts.annunciator.FreeTTSAnnunciator diff --git a/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/VoiceDemo.java b/app/alarm/freetts-annunciator/src/test/java/org/phoebus/applications/alarm/freetts/annunciator/VoiceDemo.java similarity index 94% rename from app/alarm/ui/src/test/java/org/phoebus/applications/alarm/VoiceDemo.java rename to app/alarm/freetts-annunciator/src/test/java/org/phoebus/applications/alarm/freetts/annunciator/VoiceDemo.java index 49ba15fd7b..43e192e704 100644 --- a/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/VoiceDemo.java +++ b/app/alarm/freetts-annunciator/src/test/java/org/phoebus/applications/alarm/freetts/annunciator/VoiceDemo.java @@ -5,7 +5,7 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html ******************************************************************************/ -package org.phoebus.applications.alarm; +package org.phoebus.applications.alarm.freetts.annunciator; import com.sun.speech.freetts.Voice; import com.sun.speech.freetts.VoiceManager; diff --git a/app/alarm/pom.xml b/app/alarm/pom.xml index a5b1307493..71e072b45e 100644 --- a/app/alarm/pom.xml +++ b/app/alarm/pom.xml @@ -12,5 +12,7 @@ ui logging-ui datasource + freetts-annunciator + audio-annunciator diff --git a/app/alarm/ui/pom.xml b/app/alarm/ui/pom.xml index d36aa6b31f..de1c177062 100644 --- a/app/alarm/ui/pom.xml +++ b/app/alarm/ui/pom.xml @@ -44,10 +44,6 @@ app-alarm-model 4.7.4-SNAPSHOT - - net.sf.sociaal - freetts - 1.2.2 - + diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/Annunciator.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/Annunciator.java index 2cda2a5163..8a4beff8d2 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/Annunciator.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/Annunciator.java @@ -1,51 +1,16 @@ -/******************************************************************************* - * Copyright (c) 2018 Oak Ridge National Laboratory. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - *******************************************************************************/ package org.phoebus.applications.alarm.ui.annunciator; -import com.sun.speech.freetts.Voice; -import com.sun.speech.freetts.VoiceManager; - -/** - * Annunciator class. Uses freeTTS to annunciate passed messages. - * @author Evan Smith - */ -@SuppressWarnings("nls") -public class Annunciator +public interface Annunciator { - private final VoiceManager voiceManager; - private final Voice voice; - private static final String voice_name = "kevin16"; - - /** Constructor */ - public Annunciator() - { - // Define the voices directory. - System.setProperty("freetts.voices", "com.sun.speech.freetts.en.us.cmu_us_kal.KevinVoiceDirectory"); - voiceManager = VoiceManager.getInstance(); - voice = voiceManager.getVoice(voice_name); - voice.allocate(); - } /** * Annunciate the message. Only returns once speaking finishes. * @param message Message text */ - public void speak(final String message) - { - if (null != message) - voice.speak(message); - } + void speak(final AnnunciatorMessage message); /** - * Deallocates the voice. + * Release resources that need to be cleaned on shutdown. */ - public void shutdown() - { - voice.deallocate(); - } + default void shutdown(){} } diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java index 22a9a12751..a550148a72 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/annunciator/AnnunciatorController.java @@ -10,11 +10,14 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; +import java.util.ServiceLoader; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.function.Consumer; +import java.util.stream.Collectors; import org.phoebus.applications.alarm.model.SeverityLevel; +import org.phoebus.framework.adapter.AdapterFactory; /** Controller class for an annunciator. * @@ -44,7 +47,8 @@ public class AnnunciatorController private final BlockingQueue to_annunciate = new LinkedBlockingQueue<>(); - private final Annunciator annunciator = new Annunciator(); + private final List annunciators; + private final Thread process_thread = new Thread(this::processMessages, "Annunciator"); // Muted _IS_ read from multiple threads, so it should always be fetched from memory. @@ -59,6 +63,10 @@ public AnnunciatorController(final int threshold, final Consumer loader = ServiceLoader.load(Annunciator.class); + annunciators = loader.stream().map(ServiceLoader.Provider::get).collect(Collectors.toList()); + // The thread should exit when requested by shutdown() call, but set to daemon so it dies // when program closes regardless. process_thread.setDaemon(true); @@ -115,7 +123,9 @@ private void processMessages() { addToTable.accept(message); if (! muted) - annunciator.speak(message.message); + annunciators.stream().forEach(annunciator -> { + annunciator.speak(message); + }); } } else @@ -130,7 +140,11 @@ private void processMessages() { // Annunciate if marked as stand out. addToTable.accept(message); if (! muted) - annunciator.speak(message.message); + { + annunciators.stream().forEach(annunciator -> { + annunciator.speak(message); + }); + } } else { // Increment count of non stand out messages. @@ -144,7 +158,11 @@ private void processMessages() final AnnunciatorMessage message = new AnnunciatorMessage(false, null, earliest, "There are " + flurry + " new messages"); addToTable.accept(message); if (! muted) - annunciator.speak(message.message); + { + annunciators.stream().forEach(annunciator -> { + annunciator.speak(message); + }); + } } } } @@ -157,10 +175,13 @@ public void shutdown() throws InterruptedException { // Send magic message that wakes annunciatorThread and causes it to exit to_annunciate.offer(LAST_MESSAGE); + // The thread should shutdown process_thread.join(2000); // Deallocate the annunciator's voice. - annunciator.shutdown(); + annunciators.stream().forEach(annunciator -> { + annunciator.shutdown(); + }); } } diff --git a/phoebus-product/pom.xml b/phoebus-product/pom.xml index 66b9b5b2e5..4815bb3803 100644 --- a/phoebus-product/pom.xml +++ b/phoebus-product/pom.xml @@ -186,6 +186,12 @@ app-alarm-ui 4.7.4-SNAPSHOT + + org.phoebus + app-alarm-freetts-annunciator + 4.7.4-SNAPSHOT + true + org.phoebus app-alarm-logging-ui