/*
Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
и других спецификаций для функционирования компактных приложений на языке
Java (мидлетов) в среде программного обеспечения Малик Эмулятор.
Copyright © 2016–2017, 2019–2023 Малик Разработчик
Это свободная программа: вы можете перераспространять ее и/или изменять
ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
общественной лицензии GNU.
Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<https://www.gnu.org/licenses/>.
*/
package com.nokia.mid.sound;
import java.io.*;
import javax.microedition.media.*;
import javax.microedition.media.control.*;
public class Sound extends Object
{
public static final int FORMAT_TONE = 1; /* устаревший формат */
public static final int FORMAT_WAV = 5;
public static final int SOUND_PLAYING = 0;
public static final int SOUND_STOPPED = 1;
public static final int SOUND_UNINITIALIZED = 3;
private static boolean formatToneNotImplementedNotified;
private static final byte[] EMPTY_MIDI;
static {
EMPTY_MIDI = new byte[] {
(byte) 0x4d, (byte) 0x54, (byte) 0x68, (byte) 0x64,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x06,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x64,
(byte) 0x4d, (byte) 0x54, (byte) 0x72, (byte) 0x6b,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x0c,
(byte) 0x00, (byte) 0xff, (byte) 0x51, (byte) 0x03, (byte) 0x01, (byte) 0x86, (byte) 0xa0,
(byte) 0x87, (byte) 0x68, (byte) 0xff, (byte) 0x2f, (byte) 0x00
};
}
public static int getConcurrentSoundCount(int format) {
switch(format)
{
default:
throw new IllegalArgumentException("Sound.getConcurrentSoundCount: аргумент format имеет недопустимое значение.");
case FORMAT_TONE:
case FORMAT_WAV:
return 255;
}
}
public static int[] getSupportedFormats() {
return new int[] { FORMAT_TONE, FORMAT_WAV };
}
private int state;
private Player implement;
private VolumeControl volume;
private SoundListener listener;
private final PlayerListener gate;
public Sound(int frequency, long duration) {
this.gate = createGate();
recreate(frequency, duration);
}
public Sound(byte[] data, int format) {
this.gate = createGate();
recreate(data, format);
}
public void init(int frequency, long duration) {
recreate(frequency, duration);
}
public void init(byte[] data, int format) {
recreate(data, format);
}
public void release() {
Player implement;
if((implement = this.implement) != null)
{
this.state = SOUND_UNINITIALIZED;
this.implement = null;
this.volume = null;
implement.close();
}
}
public void resume() {
Player implement;
if((implement = this.implement) != null)
{
this.state = SOUND_PLAYING;
try
{
implement.start();
}
catch(MediaException e)
{
e.printRealStackTrace();
}
}
}
public void play(int loopCount) {
Player implement;
if(loopCount < 0)
{
throw new IllegalArgumentException("Sound.play: аргумент loopCount не может быть отрицательным.");
}
if(loopCount >= 255) return;
if(loopCount == 0) loopCount = -1;
if((implement = this.implement) != null)
{
this.state = SOUND_PLAYING;
try
{
implement.stop();
implement.setLoopCount(loopCount);
implement.start();
}
catch(MediaException e)
{
e.printRealStackTrace();
}
}
}
public void stop() {
Player implement;
if((implement = this.implement) != null)
{
this.state = SOUND_STOPPED;
try
{
implement.stop();
}
catch(MediaException e)
{
e.printRealStackTrace();
}
}
}
public void setSoundListener(SoundListener listener) {
this.listener = listener;
}
public void setGain(int volumeLevel) {
VolumeControl volume;
if(volumeLevel < 0) volumeLevel = 0;
if(volumeLevel > 255) volumeLevel = 255;
if(volumeLevel > 0) volumeLevel = ((volumeLevel - 1) * 99 / 254) + 1;
if((volume = this.volume) != null)
{
try
{
volume.setLevel(volumeLevel);
}
catch(Exception e)
{
e.printRealStackTrace();
}
}
}
public int getGain() {
int result;
VolumeControl volume;
if((volume = this.volume) == null) return -1;
if((result = volume.getLevel()) > 0) result = ((result - 1) * 254 / 99) + 1;
return result;
}
public int getState() {
return state;
}
final void notify(int state) {
SoundListener listener;
this.state = state;
if((listener = this.listener) != null) listener.soundStateChanged(this, state);
}
private strictfp void recreate(int frequency, long duration) {
int time;
int note;
byte[] midi;
Player implement;
if(frequency < 0)
{
throw new IllegalArgumentException("Sound.init: аргумент frequency не может быть отрицательным.");
}
if(duration <= 0L)
{
throw new IllegalArgumentException("Sound.init: аргумент duration может быть только положительным.");
}
time = duration > 10000L ? 10000 : (int) duration;
if((note = frequency <= 0 ? 0 : (int) Math.round(12.d * Math.log2((double) frequency) + (-36.3763165622959152d))) < 0x00)
{
note = 0x00;
}
else if(note > 0x7f)
{
note = 0x7f;
}
midi = new byte[] {
(byte) 0x4d, (byte) 0x54, (byte) 0x68, (byte) 0x64,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x06,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x64,
(byte) 0x4d, (byte) 0x54, (byte) 0x72, (byte) 0x6b,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x14,
(byte) 0x00, (byte) 0xff, (byte) 0x51, (byte) 0x03, (byte) 0x01, (byte) 0x86, (byte) 0xa0,
(byte) 0x00, (byte) 0x90, (byte) note, (byte) 0x7f,
(byte) (0x80 | time >> 7), (byte) (time & 0x7f), (byte) note, (byte) 0x00,
(byte) (0x80 | time >> 7), (byte) (time & 0x7f), (byte) 0xff, (byte) 0x2f, (byte) 0x00
};
if((implement = this.implement) != null) implement.close();
try
{
init(Manager.createPlayer(new ByteArrayInputStream(midi), "audio/midi"));
}
catch(Exception e)
{
e.printRealStackTrace();
}
}
private void recreate(byte[] data, int format) {
Player implement;
if(data == null)
{
throw new NullPointerException("Sound.init: аргумент data равен нулевой ссылке.");
}
switch(format)
{
case FORMAT_TONE:
if(!formatToneNotImplementedNotified)
{
formatToneNotImplementedNotified = true;
System.out.println("/!\\ Sound.init: формат TONE устарел и не поддерживается этой реализацией.");
}
if((implement = this.implement) != null) implement.close();
try
{
init(Manager.createPlayer(new ByteArrayInputStream(EMPTY_MIDI), "audio/midi"));
}
catch(Exception e)
{
throw new IllegalArgumentException(e.getMessage());
}
break;
case FORMAT_WAV:
if((implement = this.implement) != null) implement.close();
try
{
init(Manager.createPlayer(new ByteArrayInputStream(data), "audio/x-wav"));
}
catch(Exception e)
{
throw new IllegalArgumentException(e.getMessage());
}
break;
default:
throw new IllegalArgumentException("Sound.init: аргумент format имеет недопустимое значение.");
}
}
private void init(Player implement) throws MediaException {
VolumeControl volume;
implement.addPlayerListener(gate);
implement.prefetch();
volume = (VolumeControl) implement.getControl("VolumeControl");
this.state = SOUND_STOPPED;
this.implement = implement;
this.volume = volume;
}
private PlayerListener createGate() {
return new PlayerListener() {
public void playerUpdate(Player player, String event, Object data) {
Sound parent = Sound.this;
if(CLOSED.equals(event))
{
parent.notify(Sound.SOUND_UNINITIALIZED);
return;
}
if(STARTED.equals(event))
{
parent.notify(Sound.SOUND_PLAYING);
return;
}
if(STOPPED.equals(event) || END_OF_MEDIA.equals(event)) parent.notify(Sound.SOUND_STOPPED);
}
};
}
}