package jp.kshoji.blemidi.sample;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.wearable.activity.WearableActivity;
import android.support.wearable.view.WatchViewStub;
import android.view.View;
import android.widget.TextView;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import jp.kshoji.blemidi.central.BleMidiCentralProvider;
import jp.kshoji.blemidi.device.MidiInputDevice;
import jp.kshoji.blemidi.device.MidiOutputDevice;
import jp.kshoji.blemidi.listener.OnMidiDeviceAttachedListener;
import jp.kshoji.blemidi.listener.OnMidiDeviceDetachedListener;
import jp.kshoji.blemidi.listener.OnMidiInputEventListener;
import jp.kshoji.blemidi.sample.util.SoundMaker;
import jp.kshoji.blemidi.sample.util.Tone;
public class SynthesizerActivity extends WearableActivity implements OnMidiInputEventListener {
private TextView textView;
private TextView noteView;
private TextView titleView;
private View background;
// Play sounds
AudioTrack audioTrack;
Timer timer;
TimerTask timerTask;
SoundMaker soundMaker;
final Set<Tone> tones = new HashSet<>();
int currentProgram = 0;
BleMidiCentralProvider bleMidiCentralProvider;
private String[] notenames;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_synthesizer);
final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
@Override
public void onLayoutInflated(WatchViewStub stub) {
textView = (TextView) stub.findViewById(R.id.text);
titleView = (TextView) stub.findViewById(R.id.title);
background = stub.findViewById(R.id.background);
noteView = (TextView) stub.findViewById(R.id.note);
}
});
bleMidiCentralProvider = new BleMidiCentralProvider(this);
notenames = getResources().getStringArray(R.array.notenames);
setAmbientEnabled();
}
@Override
protected void onDestroy() {
super.onDestroy();
bleMidiCentralProvider = null;
textView = null;
titleView = null;
background = null;
}
@Override
protected void onResume() {
super.onResume();
bleMidiCentralProvider.setOnMidiDeviceAttachedListener(new OnMidiDeviceAttachedListener() {
@Override
public void onMidiInputDeviceAttached(@NonNull MidiInputDevice midiInputDevice) {
midiInputDevice.setOnMidiInputEventListener(SynthesizerActivity.this);
runOnUiThread(new Runnable() {
@Override
public void run() {
if (textView != null) {
textView.setText(getString(R.string.connected));
}
}
});
}
@Override
public void onMidiOutputDeviceAttached(@NonNull final MidiOutputDevice midiOutputDevice) {
// do nothing
}
});
bleMidiCentralProvider.setOnMidiDeviceDetachedListener(new OnMidiDeviceDetachedListener() {
@Override
public void onMidiInputDeviceDetached(@NonNull MidiInputDevice midiInputDevice) {
midiInputDevice.setOnMidiInputEventListener(null);
runOnUiThread(new Runnable() {
@Override
public void run() {
if (bleMidiCentralProvider == null) {
return;
}
if (bleMidiCentralProvider.getMidiInputDevices().size() <= 1) {
if (textView != null) {
textView.setText(getString(R.string.no_devices_found));
}
}
noteView.setText("");
}
});
}
@Override
public void onMidiOutputDeviceDetached(@NonNull final MidiOutputDevice midiOutputDevice) {
// do nothing
}
});
bleMidiCentralProvider.startScanDevice(-1);
soundMaker = SoundMaker.getInstance();
final int bufferSize = AudioTrack.getMinBufferSize(soundMaker.getSamplingRate(), AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
int timerRate = bufferSize * 1000 / soundMaker.getSamplingRate() / 2;
final short[] wav = new short[bufferSize / 2];
audioTrack = prepareAudioTrack(soundMaker.getSamplingRate());
timer = new Timer();
timerTask = new TimerTask() {
@Override
public void run() {
if (soundMaker != null) {
synchronized (tones) {
for (int i = 0; i < wav.length; i++) {
wav[i] = (short) (soundMaker.makeWaveStream(tones) * 1024);
}
}
try {
if (audioTrack != null) {
audioTrack.write(wav, 0, wav.length);
}
} catch (IllegalStateException | NullPointerException e) {
// do nothing
}
}
}
};
timer.scheduleAtFixedRate(timerTask, 10, timerRate);
}
@Override
public void onEnterAmbient(Bundle ambientDetails) {
super.onEnterAmbient(ambientDetails);
textView.getPaint().setAntiAlias(false);
titleView.getPaint().setAntiAlias(false);
noteView.getPaint().setAntiAlias(false);
background.setBackgroundColor(0);
}
@Override
public void onExitAmbient() {
super.onExitAmbient();
textView.getPaint().setAntiAlias(true);
titleView.getPaint().setAntiAlias(true);
noteView.getPaint().setAntiAlias(true);
background.setBackgroundColor(0xff80a0f0);
}
/**
* @param samplingRate sampling rate for playing
* @return configured {@link AudioTrack} instance
*/
private static AudioTrack prepareAudioTrack(int samplingRate) {
AudioTrack result = new AudioTrack(AudioManager.STREAM_MUSIC, samplingRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, AudioTrack.getMinBufferSize(samplingRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT), AudioTrack.MODE_STREAM);
result.setStereoVolume(1f, 1f);
result.play();
return result;
}
@Override
protected void onPause() {
super.onPause();
bleMidiCentralProvider.stopScanDevice();
if (timer != null) {
try {
timer.cancel();
timer.purge();
} catch (Throwable t) {
// do nothing
} finally {
timer = null;
}
}
if (audioTrack != null) {
try {
audioTrack.stop();
audioTrack.flush();
audioTrack.release();
} catch (Throwable t) {
// do nothing
} finally {
audioTrack = null;
}
}
}
@Override
public void onMidiSystemExclusive(@NonNull MidiInputDevice midiInputDevice, @NonNull byte[] bytes) {
}
@Override
public void onMidiNoteOff(@NonNull MidiInputDevice midiInputDevice, int channel, int note, int velocity) {
synchronized (tones) {
Iterator<Tone> it = tones.iterator();
while (it.hasNext()) {
Tone tone = it.next();
if (tone.getNote() == note) {
it.remove();
}
}
if (tones.size() < 1) {
runOnUiThread(new Runnable() {
@Override
public void run() {
noteView.setText("");
}
});
}
}
}
@Override
public void onMidiNoteOn(@NonNull MidiInputDevice midiInputDevice, int channel, final int note, int velocity) {
synchronized (tones) {
if (velocity == 0) {
Iterator<Tone> it = tones.iterator();
while (it.hasNext()) {
Tone tone = it.next();
if (tone.getNote() == note) {
it.remove();
}
}
if (tones.size() < 1) {
runOnUiThread(new Runnable() {
@Override
public void run() {
noteView.setText("");
}
});
}
} else {
runOnUiThread(new Runnable() {
@Override
public void run() {
noteView.setText(notenames[note % 12]);
}
});
tones.add(new Tone(note, velocity / 127.0, currentProgram));
}
}
}
@Override
public void onMidiPolyphonicAftertouch(@NonNull MidiInputDevice midiInputDevice, int i, int i1, int i2) {
}
@Override
public void onMidiControlChange(@NonNull MidiInputDevice midiInputDevice, int i, int i1, int i2) {
}
@Override
public void onMidiProgramChange(@NonNull MidiInputDevice midiInputDevice, int channel, int program) {
currentProgram = program % Tone.FORM_MAX;
synchronized (tones) {
for (Tone tone : tones) {
tone.setForm(currentProgram);
}
}
}
@Override
public void onMidiChannelAftertouch(@NonNull MidiInputDevice midiInputDevice, int i, int i1) {
}
@Override
public void onMidiPitchWheel(@NonNull MidiInputDevice midiInputDevice, int i, int i1) {
}
@Override
public void onMidiTimeCodeQuarterFrame(@NonNull MidiInputDevice midiInputDevice, int i) {
}
@Override
public void onMidiSongSelect(@NonNull MidiInputDevice midiInputDevice, int i) {
}
@Override
public void onMidiSongPositionPointer(@NonNull MidiInputDevice midiInputDevice, int i) {
}
@Override
public void onMidiTuneRequest(@NonNull MidiInputDevice midiInputDevice) {
}
@Override
public void onMidiTimingClock(@NonNull MidiInputDevice midiInputDevice) {
}
@Override
public void onMidiStart(@NonNull MidiInputDevice midiInputDevice) {
}
@Override
public void onMidiContinue(@NonNull MidiInputDevice midiInputDevice) {
}
@Override
public void onMidiStop(@NonNull MidiInputDevice midiInputDevice) {
}
@Override
public void onMidiActiveSensing(@NonNull MidiInputDevice midiInputDevice) {
}
@Override
public void onMidiReset(@NonNull MidiInputDevice midiInputDevice) {
}
@Override
public void onRPNMessage(@NonNull MidiInputDevice midiInputDevice, int i, int i1, int i2) {
}
@Override
public void onNRPNMessage(@NonNull MidiInputDevice midiInputDevice, int i, int i1, int i2) {
}
}