Skip to content

Commit

Permalink
Manager: + nokia: Sound: Improve tone playing accuracy.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
AShiningRay committed Feb 25, 2025
1 parent 7cf1564 commit 56e1c97
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 14 deletions.
11 changes: 6 additions & 5 deletions src/com/nokia/mid/sound/Sound.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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); }
Expand Down
21 changes: 12 additions & 9 deletions src/javax/microedition/media/Manager.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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");

Expand All @@ -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."); }
Expand All @@ -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)
Expand Down

0 comments on commit 56e1c97

Please sign in to comment.