• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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