1 /* <lambda>null2 * Copyright 2019 Google LLC 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * https://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.mobileer.androidfxlab 18 19 import android.Manifest 20 import android.content.Context 21 import android.content.pm.PackageManager 22 import android.media.midi.MidiDeviceInfo 23 import android.media.midi.MidiManager 24 import android.media.midi.MidiReceiver 25 import android.os.Build 26 import android.os.Bundle 27 import android.os.Handler 28 import android.util.Log 29 import android.view.Menu 30 import android.view.MenuItem 31 import android.view.SubMenu 32 import android.view.WindowManager 33 import android.widget.PopupMenu 34 import android.widget.SeekBar 35 import androidx.annotation.RequiresApi 36 import androidx.appcompat.app.AlertDialog 37 import androidx.appcompat.app.AppCompatActivity 38 import androidx.core.app.ActivityCompat 39 import androidx.core.content.ContextCompat 40 import androidx.databinding.DataBindingUtil 41 import com.mobileer.androidfxlab.databinding.ActivityMainBinding 42 import com.mobileer.androidfxlab.datatype.Effect 43 44 class MainActivity : AppCompatActivity() { 45 46 private var TAG: String = this.toString() 47 lateinit var binding: ActivityMainBinding 48 private var isAudioEnabled: Boolean = false 49 50 val MY_PERMISSIONS_RECORD_AUDIO = 17 51 52 @RequiresApi(Build.VERSION_CODES.M) 53 override fun onCreate(savedInstanceState: Bundle?) { 54 super.onCreate(savedInstanceState) 55 binding = DataBindingUtil.setContentView(this, R.layout.activity_main) 56 setSupportActionBar(binding.toolbar) 57 58 window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) 59 60 if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) 61 != PackageManager.PERMISSION_GRANTED 62 ) { 63 ActivityCompat.requestPermissions( 64 this, 65 arrayOf(Manifest.permission.RECORD_AUDIO), 66 MY_PERMISSIONS_RECORD_AUDIO 67 ) 68 } 69 70 binding.effectListView.adapter = EffectsAdapter 71 72 binding.floatingAddButton.setOnClickListener { view -> 73 val popup = PopupMenu(this, view) 74 popup.menuInflater.inflate(R.menu.add_menu, popup.menu) 75 val menuMap = HashMap<String, SubMenu>() 76 for (effectName in NativeInterface.effectDescriptionMap.keys) { 77 val cat = NativeInterface.effectDescriptionMap.getValue(effectName).category 78 if (cat == "None") { 79 popup.menu.add(effectName) 80 } else { 81 val subMenu = menuMap[cat] ?: popup.menu.addSubMenu(cat) 82 subMenu.add(effectName) 83 menuMap[cat] = subMenu 84 } 85 } 86 popup.setOnMenuItemClickListener { menuItem -> 87 NativeInterface.effectDescriptionMap[menuItem.title]?.let { 88 val toAdd = Effect(it) 89 EffectsAdapter.effectList.add(toAdd) 90 NativeInterface.addEffect(toAdd) 91 EffectsAdapter.notifyItemInserted(EffectsAdapter.effectList.size - 1) 92 true 93 } 94 false 95 } 96 popup.show() 97 } 98 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { 99 handleMidiDevices() 100 } 101 102 } 103 104 override fun onDestroy() { 105 // Clear the FX UI 106 EffectsAdapter.effectList.clear() 107 EffectsAdapter.notifyDataSetChanged() 108 super.onDestroy() 109 } 110 111 override fun onPause() { 112 // Shutdown Engine 113 NativeInterface.destroyAudioEngine() 114 super.onPause() 115 } 116 117 override fun onResume() { 118 super.onResume() 119 // Startup Engine 120 if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) 121 == PackageManager.PERMISSION_GRANTED 122 ) { 123 NativeInterface.createAudioEngine() 124 NativeInterface.enable(isAudioEnabled) 125 } 126 } 127 128 override fun onRequestPermissionsResult( 129 requestCode: Int, 130 permissions: Array<out String>, 131 grantResults: IntArray 132 ) { 133 super.onRequestPermissionsResult(requestCode, permissions, grantResults) 134 when (requestCode) { 135 MY_PERMISSIONS_RECORD_AUDIO -> { 136 if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { 137 NativeInterface.createAudioEngine() 138 } else { 139 val builder = AlertDialog.Builder(this).apply { 140 setMessage( 141 "Audio effects require audio input permissions! \n" + 142 "Enable permissions and restart app to use." 143 ) 144 setTitle("Permission Error") 145 } 146 builder.create().show() 147 } 148 return 149 } 150 } 151 } 152 153 @RequiresApi(Build.VERSION_CODES.M) 154 private fun handleMidiDevices() { 155 156 val midiManager = getSystemService(Context.MIDI_SERVICE) as MidiManager 157 midiManager.registerDeviceCallback(object : MidiManager.DeviceCallback() { 158 override fun onDeviceAdded(device: MidiDeviceInfo) { 159 160 // open this device 161 midiManager.openDevice(device, { 162 Log.d(TAG, "Opened MIDI device") 163 164 val targetSeekBar = findViewById<SeekBar>(R.id.seekBar) 165 if (targetSeekBar != null) { 166 167 val midiReceiver = MyMidiReceiver(targetSeekBar) 168 val outputPort = it.openOutputPort(0) 169 outputPort?.connect(midiReceiver) 170 } 171 172 }, Handler()) 173 } 174 }, Handler()) 175 } 176 177 override fun onCreateOptionsMenu(menu: Menu): Boolean { 178 getMenuInflater().inflate(R.menu.toolbar_menu, menu) 179 return true 180 } 181 182 override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { 183 R.id.action_toggle_mute -> { 184 isAudioEnabled = !isAudioEnabled 185 NativeInterface.enable(isAudioEnabled) 186 187 if (isAudioEnabled) { 188 item.setIcon(R.drawable.ic_baseline_audio_is_enabled_24) 189 } else { 190 item.setIcon(R.drawable.ic_baseline_audio_is_disabled_24) 191 } 192 true 193 } 194 else -> { 195 super.onOptionsItemSelected(item) 196 } 197 } 198 199 @RequiresApi(Build.VERSION_CODES.M) 200 class MyMidiReceiver(var seekBar: SeekBar) : MidiReceiver() { 201 202 private val TAG: String = "MyMidiReceiver" 203 204 override fun onSend(data: ByteArray?, offset: Int, count: Int, timestamp: Long) { 205 206 Log.d(TAG, "Got midi message, offset " + offset + " count " + count) 207 Log.d(TAG, "Byte 0 " + Integer.toHexString(data!![offset].toInt())) 208 Log.d(TAG, "Byte 1 " + Integer.toHexString(data[offset+1].toInt())) 209 Log.d(TAG, "Byte 2 " + data[offset+2].toInt()) 210 211 val CONTROL_CHANGE_CH1 : Byte = 0xB0.toByte() 212 213 if (data[offset] == CONTROL_CHANGE_CH1){ 214 seekBar.progress = (data[offset+2].toInt() / 1.27).toInt() 215 } 216 } 217 } 218 219 } 220