From 56e1c97583e4ee2d069bebc43cd3d159478ae0f0 Mon Sep 17 00:00:00 2001 From: AShiningRay Date: Tue, 25 Feb 2025 13:15:23 -0300 Subject: [PATCH] Manager: + nokia: Sound: Improve tone playing accuracy. Now it should probably interrupt and stop playing previous tones if a new one is sent to be played, to better mirror the single note aspect of playTone(). Validation checks were also added to nokia Sound's init methods. --- src/com/nokia/mid/sound/Sound.java | 11 ++++++----- src/javax/microedition/media/Manager.java | 21 ++++++++++++--------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/com/nokia/mid/sound/Sound.java b/src/com/nokia/mid/sound/Sound.java index 7b715e98..711b9b5e 100644 --- a/src/com/nokia/mid/sound/Sound.java +++ b/src/com/nokia/mid/sound/Sound.java @@ -104,6 +104,9 @@ public int getState() public void init(byte[] data, int type) { + if(type != FORMAT_TONE && type != FORMAT_WAV) { throw new IllegalArgumentException("Cannot init player with unsupported format"); } + if(data == null) { throw new NullPointerException("Cannot init player with null data"); } + try { if (type == FORMAT_TONE) @@ -145,12 +148,10 @@ else if (type == FORMAT_WAV) catch (MediaException exception) { } catch (IOException exception) { } } - /* - * Haven't found a jar using this yet, but forcing it through the one above does indicate that it works even if incorrectly - * Also, based on the j2megame source, this is just javax.microedition.media.Manager.playTone() on MIDP 2.0 - */ public void init(int freq, long duration) - { + { + if(duration <= 0 || convertFreqToNote(freq) > 127 || convertFreqToNote(freq) < 0) { throw new IllegalArgumentException("Cannot init tone with invalid parameters"); } + Mobile.log(Mobile.LOG_DEBUG, Sound.class.getPackage().getName() + "." + Sound.class.getSimpleName() + ": " + "Nokia Sound: Single Note:" + freq); try { Manager.playTone(convertFreqToNote(freq), (int) duration, TONE_MAX_VOLUME); } diff --git a/src/javax/microedition/media/Manager.java b/src/javax/microedition/media/Manager.java index 25c4368a..6a750745 100644 --- a/src/javax/microedition/media/Manager.java +++ b/src/javax/microedition/media/Manager.java @@ -53,6 +53,7 @@ public class Manager public static Synthesizer mainSynth; private static Synthesizer dedicatedTonePlayer = null; private static MidiChannel dedicatedToneChannel; + private static Thread toneThread; public static synchronized Player createPlayer(InputStream stream, String type) throws IOException, MediaException { @@ -161,6 +162,8 @@ public static String[] getSupportedProtocols(String content_type) public static void playTone(int note, int duration, int volume) throws MediaException { + if(Mobile.sound == false) { return; } + checkCustomMidi(); Mobile.log(Mobile.LOG_DEBUG, Manager.class.getPackage().getName() + "." + Manager.class.getSimpleName() + ": " + "Play Tone"); @@ -182,10 +185,8 @@ public static void playTone(int note, int duration, int volume) throws MediaExce } catch (MidiUnavailableException e) { Mobile.log(Mobile.LOG_ERROR, Manager.class.getPackage().getName() + "." + Manager.class.getSimpleName() + ": " + "Couldn't open Tone Player: " + e.getMessage()); return;} } - else - { - for (int stopNote = 0; stopNote <= 127; stopNote++) { dedicatedToneChannel.noteOff(note);} - } + + if(toneThread != null && toneThread.isAlive()) { toneThread.interrupt(); } // Interrupt the currently playing tone if one is playing // Notes that are too short can't even be heard in FreeJ2ME (some Karma Studios games use 10ms for sound, which is barely enough time for the media to start playing). A reasonable minimum duration is 50ms. if(duration < 50) { Mobile.log(Mobile.LOG_DEBUG, Manager.class.getPackage().getName() + "." + Manager.class.getSimpleName() + ": " + "Tone duration too short (" + duration + " ms), changing to the 50ms min."); } @@ -200,12 +201,14 @@ public static void playTone(int note, int duration, int volume) throws MediaExce dedicatedToneChannel.noteOn(note, effectiveDuration); // Make the decay just long enough for the note not to fade shorter than expected /* Since it has to be non-blocking, wait for the specified duration in a separate Thread before stopping the note. */ - new Thread(() -> + toneThread = new Thread(() -> { - try { Thread.sleep(effectiveDuration); } - catch (InterruptedException e) { Mobile.log(Mobile.LOG_ERROR, Manager.class.getPackage().getName() + "." + Manager.class.getSimpleName() + ": " + "Failed to keep playing note for its specified duration: " + e.getMessage()); } - dedicatedToneChannel.noteOff(note); - }).start(); + try { Thread.sleep(effectiveDuration); } + catch (InterruptedException e) { dedicatedToneChannel.noteOff(note); } // Stop playing earlier if interrupted + dedicatedToneChannel.noteOff(note); + }); + + toneThread.start(); } public static final InputStream dumpAudioStream(InputStream stream, String type)