独自のSoundFontエンジンを使っていたとき、この方法で鳴らしていました。
しかし、SoundFontの計算をすべてJavaで行うのは、無理があるらしく、最初の音がかならず割れてしまいます。
結局、WindowsでもAndroidでも、のちにFluidSynthを用いる方向に変更しています。
そちらは、別の記事として、掲載を予定しています。
package org.star_advance.mixtone.synth.audio;
import android.util.Log;
import org.star_advance.mih.midi.libs.MXQueue;
import org.star_advance.mixtone.synth.XTSynthesizer;
import org.star_advance.mixtone.synth.XTSynthesizerSetting;
import org.star_advance.mixtone.synth.oscilator.XTOscilator;
import java.util.LinkedList;
/**
*
* @author Syntarou YOSHIDA
*/
public class XTAudioStream{
static final String TAG= "XTAudioStream";
static XTAudioStream _instance;
XTSynthesizer _synth;
public static XTAudioStream getInstance(XTSynthesizer synth) {
if (_instance == null) {
_instance = new XTAudioStream(synth);
}
return _instance;
}
public XTAudioStream(XTSynthesizer synth) {
_synth = synth;
}
private AndroidSourceDataLine _sourceDL;
public double _masterVolume = 1.0;
private double[] _frame_left = null;
private double[] _frame_right = null;
Thread _renderThread = null;
public boolean isReady() {
return _renderThread != null && _sourceDL != null;
}
XTSynthesizerSetting setting = XTSynthesizerSetting.getSetting();
public void startStream() {
if (_renderThread == null) {
_sourceDL = new AndroidSourceDataLine(_synth);
_sourceDL.start();
/*
if (_copy != null) {
Log.e(TAG, "copy was "+ _copy.size());
for (int i = 0; i < _copy.size(); ++ i) {
XTOscilator osc = _copy.get(i);
Log.e(TAG, "Copy out [" + i + "] = " + osc.nextValueWithAmp() + " [" + osc.isFaded() +"]" + osc.isNoteOff() +", "+ osc._playKey + " loop " + osc._loop, osc._trace);
}
}
if (_push != null) {
Log.e(TAG, "push was "+ _push.size());
}*/
_copy = new LinkedList<>();
_push = new MXQueue<>();
if (_renderThread == null) {
_renderThread = new Thread() {
public void run() {
long start = System.currentTimeMillis();
long require = (long)(setting.getSamplePageSize() / setting.getSampleRate() * 1000);
while(true) {
if (_renderThread == null) {
break;
}
try {
updateBuffer();
long done = System.currentTimeMillis();
long dif = done - start;
start = done;
if (require - dif >= 2) {
//Log.e(TAG, "wait " + (require - dif));
//負荷がかかって音がでなくなることを回避する
//nativeメソッドがスパムに弱いので
//nativeメソッドは2000バイト一気に処理して
//またため込んで2000バイト一気に処理したりするので
//nativeメソッドが壊れ前に、リズムがおかしいので
//こちらで調整する
synchronized (this) {
try {
wait((require - dif) / 2);
start += (require - dif) / 2;
}catch (InterruptedException ex) {
}
}
}
}catch(Throwable ex) {
ex.printStackTrace();
}
}
_renderThread = null;
_sourceDL.stop();
_sourceDL = null;
}
};
_renderThread.start();
}
}
}
public void stopStream() {
if (_renderThread != null) {
_renderThread = null;
}
}
long _lastAllOff = -1;
public boolean updateBuffer(){
if (_sourceDL == null) {
return false;
}
XTSynthesizerSetting setting = XTSynthesizerSetting.getSetting();
int frameSize = (int)setting.getSamplePageSize();
if (_frame_left == null || _frame_left.length != frameSize) {
_frame_left = new double[frameSize];
_frame_right = new double[frameSize];
}
while (_push.isEmpty() == false) {
_copy.push(_push.pop());
}
if (setting._autoMuteMillisec > 0) {
if (_copy.size() == 0) {
if (_lastAllOff < 0) {
_lastAllOff = System.currentTimeMillis();
return false;
}
if (System.currentTimeMillis() - _lastAllOff >= setting._autoMuteMillisec) {
synchronized (_push) {
while (_copy.size() == 0 && _push.size() == 0) {
try {
_push.wait(500);
}catch (Throwable ex) {
}
}
}
_lastAllOff = -1;
}
}
else {
_lastAllOff = -1;
}
}
_sourceDL.waitWriteBuffer();
for(int i = 0; i < frameSize; i++) {
double valueLeft = 0;
double valueRight = 0;
for (int k = 0; k < _copy.size() ;++ k) {
XTOscilator j = _copy.get(k);
double v = j.nextValueWithAmp() * j._volume * j._velocity;
if (j.isFaded()) {
_copy.remove(k);
k --;
continue;
}
double pan = j._pan;
if (j._type == 2) {
pan = 1;
}else if (j._type == 4) {
pan = -1;
}
pan += 1;
pan /= 2;
double right = pan, left = 1 - pan;
valueRight += v / 2 * right;
valueLeft += v / 2 * left;
}
_frame_left[i] = valueLeft;
_frame_right[i] = valueRight;
}
_sourceDL.write(_frame_right, _frame_left);
return true;
}
LinkedList<XTOscilator> _copy = new LinkedList<>();
MXQueue<XTOscilator> _push = new MXQueue<>();
public void push(XTOscilator osc) {
_push.push(osc);
}
public void clearCopy() {
_copy.clear();
}
}
AndroidSourceDataLine.javapackage org.star_advance.mixtone.synth.audio;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;
import org.star_advance.mixandcc.MainActivity;
import org.star_advance.mih.midi.libs.MXQueue;
import org.star_advance.mixtone.synth.XTSynthesizer;
import org.star_advance.mixtone.synth.XTSynthesizerSetting;
import org.star_advance.mixtone.synth.oscilator.XTFilter;
public class AndroidSourceDataLine {
XTSynthesizerSetting setting = XTSynthesizerSetting.getSetting();
public AndroidSourceDataLine(XTSynthesizer synth) {
_synth = synth;
}
public void stop() {
_synth.allNoteOff();
pauseThread();;
}
XTSynthesizer _synth;
public void start() {
if (_audioTrack == null) {
int CONTENT_TYPE = AudioAttributes.CONTENT_TYPE_MUSIC;
int CONTENT_USAGE = AudioAttributes.USAGE_MEDIA;
int STREAM_TYPE = AudioManager.STREAM_MUSIC;
AudioManager am = MainActivity._audioManager;
int MODE = AudioTrack.MODE_STREAM;
AudioFormat.Builder format = new AudioFormat.Builder();
format.setEncoding(AudioFormat.ENCODING_PCM_16BIT);
format.setSampleRate((int)setting.getSampleRate());
format.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO);
AudioAttributes.Builder attributes = new AudioAttributes.Builder();
attributes.setContentType(CONTENT_TYPE);
attributes.setUsage(CONTENT_USAGE);
AudioTrack.Builder builder = new AudioTrack.Builder();
builder.setAudioFormat(format.build());
builder.setTransferMode(MODE);
builder.setAudioAttributes(attributes.build());
builder.setBufferSizeInBytes(setting.getSamplePageSize() * setting.getSamplePageCount() * 2);
_audioTrack = builder.build();
//_audioTrack.setPositionNotificationPeriod(_bufferSize * _bufferCount * 2);
_audioTrack.play();
}else {
_audioTrack.play();
}
if (_audioThread == null) {
_audioThread = new Thread() {
@Override
public void run() {
if (true) {
//開幕ノイズが生成される端末にたいする対処
short[] dummy = new short[setting.getSamplePageSize() * setting.getSamplePageCount() * 2];
int x = _audioTrack.write(dummy, 0, dummy.length);
}
short[] sBuffer = new short[setting.getSamplePageSize() *2 /*_bufferCount*/];
int pos = 0;
try {
while (_audioThread != null) {
while (_pool.isEmpty()) {
sumREADWAIT ++;
synchronized (_pool) {
try {
_pool.wait(500);
}catch(InterruptedException ex) {
}
}
}
AudioSegment p = _pool.pop();
for (int i = 0; i < p._right.length; i++) {
short left = (short) (p._left[i] * (10000 - 2));
short right = (short) (p._right[i] * (10000 - 2));
sBuffer[pos++] = left;
sBuffer[pos++] = right;
}
if ( _audioTrack.getBufferSizeInFrames() < pos) {
Log.e(TAG, "write error ?" + _audioTrack.getAudioAttributes());
}
if (pos >= 0/*sBuffer.length*/) {
int x = _audioTrack.write(sBuffer, 0, pos);
if (x != pos) {
sumFLUSHERROR ++;
_synth.allNoteOff();
_audioTrack.flush();
Log.e(TAG, "write error " + x + " != " + pos + ", " + _audioTrack.getAudioAttributes());
}
pos = 0;
}
_trashCan.push(p);
synchronized (_pool) {
_pool.notifyAll();
}
}
}catch (Throwable ex) {
Log.e(TAG, ex.getMessage(), ex);
}finally {
Log.e(TAG, "terminated");
if (_audioTrack != null) {
_audioTrack.stop();
_audioTrack.flush();
_audioTrack.release();
_audioTrack = null;
}
_audioThread = null;
}
}
};
_audioThread.start();
}
}
double _div = 10000.0;
MXQueue<AudioSegment> _pool = new MXQueue<>();
MXQueue<AudioSegment> _trashCan = new MXQueue<>();
class AudioSegment {
public AudioSegment() {
int size = setting.getSamplePageSize();
_right = new double[size];
_left = new double[size];
}
double[] _right;
double[] _left;
}
long sumREADWAIT = 0;
long sumOK = 0;
long sumWRITEWAIT = 0;
long sumFLUSHERROR = 0;
long prevSec = 0;
int suspend = 0;
public void waitWriteBuffer() {
synchronized (_pool) {
while (true) {
/*
long nowSec = System.currentTimeMillis() / 1000;
if (nowSec != prevSec) {
Log.e(TAG, " OK=" + sumOK + " writeWAIT=" + sumWRITEWAIT+ " readWAIT="+ sumREADWAIT + " FLUSHError=" + sumFLUSHERROR+ " avail=" + _audioTrack.getBufferSizeInFrames());
if (sumWRITEWAIT == 0) {
suspend ++;
if (suspend > 3) {
_audioTrack.flush();
}
}
else {
suspend = 0;
}
sumOK = 0;
sumWRITEWAIT = 0;
sumREADWAIT = 0;
prevSec = nowSec;
}*/
int cnt = _pool.size();
if (cnt < setting.getSamplePageCount() ) {
sumOK ++;
return;
}
else {
sumWRITEWAIT++;
}
try {
_pool.wait(500);
}catch (InterruptedException ex) {
break;
}
}
}
}
public void write(double[] right, double[] left) {
for (int i = 0; i < right.length;++ i) {
double r = right[i] * _div;
double l = left[i] * _div;
if (r <= -0.9 || r >= 0.9) {
_div *= 0.8;
i--;
continue;
}
if (l <= -0.9 || l >= 0.9) {
_div *= 0.8;
i--;
continue;
}
}
AudioSegment seg = null;
synchronized (_trashCan) {
if (!_trashCan.isEmpty()) {
seg = _trashCan.pop();
if (seg._right.length != right.length) {
seg = null;
}
}
}
if (seg == null) {
seg = new AudioSegment();
}
int x = 0;
for (int i = 0; i < right.length ;++ i) {
double r = right[i] * _div;
double l = left[i] * _div;
r = _filterR.update(r);
l = _filterL.update(l);
seg._left[x] = l;
seg._right[x] = r;
x ++;
}
synchronized (_pool) {
_pool.push(seg);
_pool.notifyAll();
}
}
XTFilter _filterL = new XTFilter();
XTFilter _filterR = new XTFilter();
static String TAG = "AndroidSourceDataLine";
private AudioTrack _audioTrack;
private Thread _audioThread;
public boolean isRunning() {
return (_audioThread != null);
}
public void pauseThread() {
_audioThread = null;
_pool = new MXQueue<>();
_trashCan = new MXQueue<>();
_filterL = new XTFilter();
_filterR = new XTFilter();
while(_audioTrack != null) {
try {
Thread.sleep(10);
}
catch (Throwable ex) {
}
}
}
}
おまけとして、WindowsなどPCのJavaのソースコードです。
JavaSourceDataLine.javapackage org.star_advance.mixtone.synth.audio;
import java.util.LinkedList;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import org.star_advance.libs.MXQueue;
import org.star_advance.mixtone.synth.XTSynthesizerSetting;
import org.star_advance.mixtone.synth.oscilator.XTFilter;
public class JavaSourceDataLine {
public void stop() {
pauseThread();
}
public void close() {
pauseThread();
}
public synchronized void launchThread() {
if (_audioTrack == null) {
try {
AudioFormat frmt= new AudioFormat(
_setting.getSampleRate(),
_setting.getSampleBits(),
_setting.getSampleChannel(),
true,
false);
DataLine.Info info= new DataLine.Info(SourceDataLine.class,frmt);
SourceDataLine dataLine = (SourceDataLine) AudioSystem.getLine(info);
dataLine.flush();
dataLine.open(frmt, _setting.getSamplePageSize() * _setting.getSamplePageCount());
dataLine.addLineListener(new LineListener() {
@Override
public void update(LineEvent event) {
}
});
_audioTrack = dataLine;
} catch (LineUnavailableException e) {
System.out.println("cant get line///");
throw new RuntimeException(e);
}
_audioTrack.flush();
_audioTrack.start();
}
if (_audioThread == null) {
_audioThread = new Thread() {
@Override
public void run() {
boolean needReboot = false;
_setting.clearUpdated();
int frame_size = _setting.getSamplePageSize();
byte[] _stereo = new byte[frame_size /** setting.getSamplePageCount() */* 4];
if (_audioThread == null) {
try {
Thread.sleep(10);
}catch(Throwable ex) {
ex.printStackTrace();
}
}
if (_audioTrack == null) {
System.err.println("Launch fatal error");
}
try {
while (_audioThread != null) {
if (_setting.isUpdated()) {
needReboot = true;
break;
}
//_pool.waitForCount(setting.getSamplePageCount());
int pos = 0;
//for (int i = 0; i < setting.getSamplePageCount(); ++ i) {
AudioSegment p = _pool.pop();
for (int x = 0; x < frame_size; x ++) {
long sampleleft = (long)(p._left[x] * 30000);
long sampleright = (long)(p._right[x] * 30000);
_stereo[pos ++] = (byte)(sampleleft & 0xff);
_stereo[pos ++] = (byte)((sampleleft >> 8) & 0xff);
_stereo[pos ++] = (byte)(sampleright & 0xff);
_stereo[pos ++] = (byte)((sampleright >> 8) & 0xff);
}
_trashCan.push(p);
//}
//if (pos >= _stereo.length) {
_audioTrack.write(_stereo,0,pos);
pos = 0;
//}
}
}finally {
if (_audioTrack != null) {
_audioTrack.stop();
_audioTrack = null;
}
_audioThread = null;
}
if (needReboot) {
JavaSourceDataLine.this.launchThread();
}
}
};
_audioThread.setDaemon(true);
_audioThread.start();
}
}
public int available() {
int x= (_setting.getSamplePageCount()- _pool.size()) * _setting.getSamplePageSize();
if (x < 0) {
return 0;
}
return x;
}
MXQueue<AudioSegment> _pool = new MXQueue<>();
MXQueue<AudioSegment> _trashCan = new MXQueue<>();
XTSynthesizerSetting _setting = XTSynthesizerSetting.getSetting();
class AudioSegment {
public AudioSegment() {
int size = _setting.getSamplePageSize();
_pos = 0;
_right = new double[size];
_left = new double[size];
}
int _pos;
double[] _right;
double[] _left;
public void write(double r, double l) {
_right[_pos] = r;
_left[_pos] = l;
_pos ++;
}
}
public void write(double[] right, double[] left) {
if (right.length == left.length) {
write(right, left, 0, right.length);
}else{
throw new IllegalArgumentException();
}
}
double _mum = 32768;
public void write(double[] right, double[] left, int pos, int length) {
double x = 0;
for (int i = pos; i < pos + length ;++ i) {
double r = right[i] * _mum;
double l = left[i] * _mum;
if (r >= 0.8 || r <= -0.8 || l >= 0.8 || l <= -0.8) {
_mum *= 0.8;
i --;
continue;
}
if (r < 0) {
x -= r;
}
else {
x += r;
}
}
AudioSegment seg;
if (!_trashCan.isEmpty()) {
seg = _trashCan.pop();
if (seg._right.length != length) {
seg = new AudioSegment();
}
}
else {
seg = new AudioSegment();
}
int pen = 0;
for (int i = pos; i < pos + length ;++ i) {
double r = right[i] * _mum;
double l = left[i] * _mum;
r = _filterR.update(r);
l = _filterL.update(l);
seg._left[pen] = l;
seg._right[pen] = r;
pen ++;
}
_pool.push(seg);
}
XTFilter _filterL = new XTFilter();
XTFilter _filterR = new XTFilter();
private Thread _audioThread;
private SourceDataLine _audioTrack;
public boolean isRunning() {
return (_audioThread != null);
}
public void pauseThread() {
_audioThread = null;
while(_audioThread != null) {
try {
Thread.sleep(10);
}
catch (Throwable ex) {
}
}
}
static String TAG = "AndroidSourceDataLine";
}
広告を表示しています。
コメント