diff --git a/android/src/com/levien/synthesizer/android/service/SynthesizerService.java b/android/src/com/levien/synthesizer/android/service/SynthesizerService.java index cca19eb..e7ea3b3 100755 --- a/android/src/com/levien/synthesizer/android/service/SynthesizerService.java +++ b/android/src/com/levien/synthesizer/android/service/SynthesizerService.java @@ -41,7 +41,7 @@ import android.util.Log; import com.levien.synthesizer.R; import com.levien.synthesizer.android.AndroidGlue; import com.levien.synthesizer.android.usb.UsbMidiDevice; -import com.levien.synthesizer.core.midi.MessageForwarder; +import com.levien.synthesizer.core.midi.MessageTee; import com.levien.synthesizer.core.midi.MidiListener; import com.levien.synthesizer.core.model.composite.MultiChannelSynthesizer; @@ -100,31 +100,7 @@ public class SynthesizerService extends Service { Log.e(getClass().getName(), "loading patches failed"); } } - midiListener_ = new MessageForwarder(androidGlue_) { - @Override - public void onController(int channel, int control, int value) { - super.onController(channel, control, value); - if (onCcListener_!= null) { - onCcListener_.onCcChange(channel, control, value); - } - } - - @Override - public void onNoteOn(int channel, int note, int velocity) { - super.onNoteOn(channel, note, velocity); - if (onNoteOnListener_ != null) { - onNoteOnListener_.onNote(channel, note, velocity); - } - } - - @Override - public void onNoteOff(int channel, int note, int velocity) { - super.onNoteOff(channel, note, velocity); - if (onNoteOffListener_ != null) { - onNoteOffListener_.onNote(channel, note, velocity); - } - } - }; + midiListener_ = new MessageTee(androidGlue_); androidGlue_.setPlayState(true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED); @@ -288,25 +264,11 @@ public class SynthesizerService extends Service { } }; - // There's a pretty good case to be made this should just tee a MidiListener instead - public interface OnCcListener { - abstract void onCcChange(int channel, int cc, int value); - } - - public interface OnNoteListener { - abstract void onNote(int channel, int note, int velocity); - } - - public void setOnCcListener(OnCcListener onCcListener) { - onCcListener_ = onCcListener; - } - - public void setOnNoteListeners(OnNoteListener onNoteOn, OnNoteListener onNoteOff) { - onNoteOnListener_ = onNoteOn; - onNoteOffListener_ = onNoteOff; + public void setMidiListener(MidiListener target) { + midiListener_.setSecondTarget(target); } - private MidiListener midiListener_; + private MessageTee midiListener_; // Binder to use for Activities in this process. private final IBinder binder_ = new LocalBinder(); @@ -322,8 +284,4 @@ public class SynthesizerService extends Service { private UsbInterface usbMidiInterface_; private UsbDevice usbDeviceNeedsPermission_; - // Plumbing for MIDI events - private OnCcListener onCcListener_; - private OnNoteListener onNoteOnListener_; - private OnNoteListener onNoteOffListener_; } diff --git a/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java b/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java index e406c0e..8e15010 100644 --- a/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java +++ b/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java @@ -16,7 +16,6 @@ package com.levien.synthesizer.android.ui; -import java.util.HashMap; import java.util.List; import android.annotation.TargetApi; @@ -29,8 +28,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; import android.os.Build; import android.os.Bundle; @@ -45,10 +42,11 @@ import android.widget.Spinner; import com.levien.synthesizer.R; import com.levien.synthesizer.android.service.SynthesizerService; -import com.levien.synthesizer.android.usb.UsbMidiDevice; import com.levien.synthesizer.android.widgets.knob.KnobListener; import com.levien.synthesizer.android.widgets.knob.KnobView; import com.levien.synthesizer.android.widgets.piano.PianoView; +import com.levien.synthesizer.core.midi.MidiAdapter; +import com.levien.synthesizer.core.midi.MidiListener; /** * Activity for simply playing the piano. @@ -70,44 +68,6 @@ public class PianoActivity2 extends Activity { overdriveKnob_ = (KnobView)findViewById(R.id.overdriveKnob); presetSpinner_ = (Spinner)findViewById(R.id.presetSpinner); - presetSpinner_.setOnItemSelectedListener(new OnItemSelectedListener() { - public void onItemSelected(AdapterView parent, View view, int position, long id) { - if (synthesizerService_ != null) { - synthesizerService_.getMidiListener().onProgramChange(0, position); - } - } - public void onNothingSelected(AdapterView parent) { - } - }); - - cutoffKnob_.setKnobListener(new KnobListener() { - public void onKnobChanged(double newValue) { - // Maybe actually bind these at synthesizer service connection time? - if (synthesizerService_ != null) { - int value = (int)Math.round(newValue * 127); - synthesizerService_.getMidiListener().onController(0, 1, value); - } - } - }); - resonanceKnob_.setKnobListener(new KnobListener() { - public void onKnobChanged(double newValue) { - if (synthesizerService_ != null) { - int value = (int)Math.round(newValue * 127); - synthesizerService_.getMidiListener().onController(0, 2, value); - } - } - }); - overdriveKnob_.setKnobListener(new KnobListener() { - public void onKnobChanged(double newValue) { - if (synthesizerService_ != null) { - int value = (int)Math.round(newValue * 127); - synthesizerService_.getMidiListener().onController(0, 3, value); - } - } - }); - - //piano_.bindTo(synthesizerService_.getMidiListener()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { setupUsbMidi(getIntent()); } @@ -212,71 +172,108 @@ public class PianoActivity2 extends Activity { public void onServiceConnected(ComponentName className, IBinder service) { SynthesizerService.LocalBinder binder = (SynthesizerService.LocalBinder)service; synthesizerService_ = binder.getService(); - piano_.bindTo(synthesizerService_.getMidiListener()); + onSynthConnected(); + } + public void onServiceDisconnected(ComponentName className) { + onSynthDisconnected(); + synthesizerService_ = null; + } + }; - // Populate patch names (note: we could update an existing list rather than - // creating a new adapter, but it probably wouldn't save all that much). - List patchNames = synthesizerService_.getPatchNames(); - ArrayAdapter adapter = new ArrayAdapter( - PianoActivity2.this, android.R.layout.simple_spinner_item, patchNames); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - presetSpinner_.setAdapter(adapter); + @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) + private void onSynthConnected() { + final MidiListener synthMidi = synthesizerService_.getMidiListener(); + piano_.bindTo(synthMidi); - // Handle any pending USB device events - if (usbDevicePending_ != null) { - synthesizerService_.connectUsbMidi(usbDevicePending_); - usbDevicePending_ = null; - } else { - UsbDevice device = synthesizerService_.usbDeviceNeedsPermission(); - if (device != null) { - synchronized (usbReceiver_) { - if (!permissionRequestPending_) { - permissionRequestPending_ = true; - UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); - usbManager.requestPermission(device, permissionIntent_); - } + cutoffKnob_.setKnobListener(new KnobListener() { + public void onKnobChanged(double newValue) { + int value = (int)Math.round(newValue * 127); + synthMidi.onController(0, 1, value); + } + }); + resonanceKnob_.setKnobListener(new KnobListener() { + public void onKnobChanged(double newValue) { + int value = (int)Math.round(newValue * 127); + synthMidi.onController(0, 2, value); + } + }); + overdriveKnob_.setKnobListener(new KnobListener() { + public void onKnobChanged(double newValue) { + int value = (int)Math.round(newValue * 127); + synthMidi.onController(0, 3, value); + } + }); + + // Connect controller changes to knob views + synthesizerService_.setMidiListener(new MidiAdapter() { + public void onNoteOn(final int channel, final int note, final int velocity) { + runOnUiThread(new Runnable() { + public void run() { + piano_.setNoteOn(note, true); } - } + }); } - // Connect controller changes to knob views - synthesizerService_.setOnCcListener(new SynthesizerService.OnCcListener() { - public void onCcChange(final int channel, final int cc, final int value) { - runOnUiThread(new Runnable() { - public void run() { - if (cc == 1) { - cutoffKnob_.setValue(value * (1.0 / 127)); - } else if (cc == 2) { - resonanceKnob_.setValue(value * (1.0 / 127)); - } else if (cc == 3) { - overdriveKnob_.setValue(value * (1.0 / 127)); - } - } - }); - } - }); - synthesizerService_.setOnNoteListeners(new SynthesizerService.OnNoteListener() { - public void onNote(final int channel, final int note, final int velocity) { - runOnUiThread(new Runnable() { - public void run() { - piano_.setNoteOn(note, true); - } - }); - } - }, new SynthesizerService.OnNoteListener() { - public void onNote(final int channel, final int note, final int velocity) { - runOnUiThread(new Runnable() { - public void run() { - piano_.setNoteOn(note, false); + public void onNoteOff(final int channel, final int note, final int velocity) { + runOnUiThread(new Runnable() { + public void run() { + piano_.setNoteOn(note, false); + } + }); + } + + public void onController(final int channel, final int cc, final int value) { + runOnUiThread(new Runnable() { + public void run() { + if (cc == 1) { + cutoffKnob_.setValue(value * (1.0 / 127)); + } else if (cc == 2) { + resonanceKnob_.setValue(value * (1.0 / 127)); + } else if (cc == 3) { + overdriveKnob_.setValue(value * (1.0 / 127)); } - }); + } + }); + } + }); + + // Populate patch names (note: we could update an existing list rather than + // creating a new adapter, but it probably wouldn't save all that much). + List patchNames = synthesizerService_.getPatchNames(); + ArrayAdapter adapter = new ArrayAdapter( + PianoActivity2.this, android.R.layout.simple_spinner_item, patchNames); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + presetSpinner_.setAdapter(adapter); + + presetSpinner_.setOnItemSelectedListener(new OnItemSelectedListener() { + public void onItemSelected(AdapterView parent, View view, int position, long id) { + synthesizerService_.getMidiListener().onProgramChange(0, position); + } + public void onNothingSelected(AdapterView parent) { + } + }); + + // Handle any pending USB device events + if (usbDevicePending_ != null) { + synthesizerService_.connectUsbMidi(usbDevicePending_); + usbDevicePending_ = null; + } else { + UsbDevice device = synthesizerService_.usbDeviceNeedsPermission(); + if (device != null) { + synchronized (usbReceiver_) { + if (!permissionRequestPending_) { + permissionRequestPending_ = true; + UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); + usbManager.requestPermission(device, permissionIntent_); + } } - }); - } - public void onServiceDisconnected(ComponentName className) { - synthesizerService_ = null; + } } - }; + } + + private void onSynthDisconnected() { + synthesizerService_.setMidiListener(null); + } private SynthesizerService synthesizerService_; diff --git a/core/src/com/levien/synthesizer/core/midi/MessageForwarder.java b/core/src/com/levien/synthesizer/core/midi/MessageTee.java similarity index 59% rename from core/src/com/levien/synthesizer/core/midi/MessageForwarder.java rename to core/src/com/levien/synthesizer/core/midi/MessageTee.java index 92092fb..d62794b 100644 --- a/core/src/com/levien/synthesizer/core/midi/MessageForwarder.java +++ b/core/src/com/levien/synthesizer/core/midi/MessageTee.java @@ -17,102 +17,172 @@ package com.levien.synthesizer.core.midi; /** - * Class for forwarding messages to another MidiListener. Great for subclassing. + * Duplicate each MIDI message to two listeners */ -public class MessageForwarder implements MidiListener { - public MessageForwarder(MidiListener target) { +public class MessageTee implements MidiListener { + public MessageTee(MidiListener target) { target_ = target; } + public void setSecondTarget(MidiListener target) { + target2_ = target; + } + // Control events. public void onNoteOff(int channel, int note, int velocity) { target_.onNoteOff(channel, note, velocity); + if (target2_ != null) { + target2_.onNoteOff(channel, note, velocity); + } } public void onNoteOn(int channel, int note, int velocity) { target_.onNoteOn(channel, note, velocity); + if (target2_ != null) { + target2_.onNoteOn(channel, note, velocity); + } } public void onNoteAftertouch(int channel, int note, int aftertouch) { target_.onNoteAftertouch(channel, note, aftertouch); + if (target2_ != null) { + target2_.onNoteAftertouch(channel, note, aftertouch); + } } public void onController(int channel, int control, int value) { target_.onController(channel, control, value); + if (target2_ != null) { + target2_.onController(channel, control, value); + } } public void onProgramChange(int channel, int program) { target_.onProgramChange(channel, program); + if (target2_ != null) { + target2_.onProgramChange(channel, program); + } } public void onChannelAftertouch(int channel, int aftertouch) { target_.onChannelAftertouch(channel, aftertouch); + if (target2_ != null) { + target2_.onChannelAftertouch(channel, aftertouch); + } } public void onPitchBend(int channel, int value) { target_.onPitchBend(channel, value); + if (target2_ != null) { + target2_.onPitchBend(channel, value); + } } // Other events. public void onTimingClock() { target_.onTimingClock(); + if (target2_ != null) { + target2_.onTimingClock(); + } } public void onActiveSensing() { target_.onActiveSensing(); + if (target2_ != null) { + target2_.onActiveSensing(); + } } // Meta events. public void onSequenceNumber(int sequenceNumber) { target_.onSequenceNumber(sequenceNumber); + if (target2_ != null) { + target2_.onSequenceNumber(sequenceNumber); + } } public void onText(byte[] text) { target_.onText(text); + if (target2_ != null) { + target2_.onText(text); + } } public void onCopyrightNotice(byte[] text) { target_.onCopyrightNotice(text); + if (target2_ != null) { + target2_.onCopyrightNotice(text); + } } public void onSequenceName(byte[] text) { target_.onSequenceName(text); + if (target2_ != null) { + target2_.onSequenceName(text); + } } public void onInstrumentName(byte[] text) { target_.onInstrumentName(text); + if (target2_ != null) { + target2_.onInstrumentName(text); + } } public void onLyrics(byte[] text) { target_.onLyrics(text); + if (target2_ != null) { + target2_.onLyrics(text); + } } public void onMarker(byte[] text) { target_.onMarker(text); + if (target2_ != null) { + target2_.onMarker(text); + } } public void onCuePoint(byte[] text) { target_.onCuePoint(text); + if (target2_ != null) { + target2_.onCuePoint(text); + } } public void onChannelPrefix(int channel) { target_.onChannelPrefix(channel); + if (target2_ != null) { + target2_.onChannelPrefix(channel); + } } public void onPort(byte[] data) { target_.onPort(data); + if (target2_ != null) { + target2_.onPort(data); + } } public void onEndOfTrack() { target_.onEndOfTrack(); + if (target2_ != null) { + target2_.onEndOfTrack(); + } } public void onSetTempo(int microsecondsPerQuarterNote) { target_.onSetTempo(microsecondsPerQuarterNote); + if (target2_ != null) { + target2_.onSetTempo(microsecondsPerQuarterNote); + } } public void onSmpteOffset(byte[] data) { target_.onSmpteOffset(data); + if (target2_ != null) { + target2_.onSmpteOffset(data); + } } public void onTimeSignature(int numerator, @@ -121,20 +191,34 @@ public class MessageForwarder implements MidiListener { int thirtySecondNotesPerQuarterNote) { target_.onTimeSignature(numerator, denominator, metronomePulse, thirtySecondNotesPerQuarterNote); + if (target2_ != null) { + target2_.onTimeSignature(numerator, denominator, metronomePulse, + thirtySecondNotesPerQuarterNote); + } } public void onKeySignature(int key, boolean isMinor) { target_.onKeySignature(key, isMinor); + if (target2_ != null) { + target2_.onKeySignature(key, isMinor); + } } public void onSequencerSpecificEvent(byte[] data) { target_.onSequencerSpecificEvent(data); + if (target2_ != null) { + target2_.onSequencerSpecificEvent(data); + } } // SysEx events. public void onSysEx(byte[] data) { target_.onSysEx(data); + if (target2_ != null) { + target2_.onSysEx(data); + } } - private MidiListener target_; + private final MidiListener target_; + private MidiListener target2_; }