Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
/captures
.externalNativeBuild
.idea/**/*
google-services.json
5 changes: 3 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ android {
}
}

apply plugin: 'com.google.gms.google-services' // Google Play services Gradle plugin

repositories {
jcenter()
maven { url 'https://jitpack.io' }
Expand Down Expand Up @@ -111,8 +113,7 @@ dependencies {
implementation 'net.iharder:base64:2.3.9'
implementation 'com.github.stupacki:MultiFunctions:1.2.1'
//implementation 'org.nield:kotlin-statistics:1.2.1'


implementation 'com.firebaseui:firebase-ui-auth:4.3.1'

implementation("org.jetbrains.kotlinx:kotlinx-io-jvm:$kotlinx_io_version") {
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-common'
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/kotlin/com/jonlatane/beatpad/Dialogs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ fun showInstrumentPicker2(
}
}
).lparams(matchParent, wrapContent)
adapter = recycler.adapter
adapter = recycler.adapter!!
}
}
}.show()
Expand Down
57 changes: 54 additions & 3 deletions app/src/main/kotlin/com/jonlatane/beatpad/PaletteEditorActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import android.os.Bundle
import android.support.constraint.ConstraintSet
import android.view.View
import android.view.WindowManager
import com.firebase.ui.auth.AuthUI
import com.firebase.ui.auth.IdpResponse
import com.google.firebase.auth.FirebaseAuth
import com.jonlatane.beatpad.model.Melody
import com.jonlatane.beatpad.model.Part
import com.jonlatane.beatpad.output.instrument.audiotrack.AudioTrackCache
Expand All @@ -21,6 +24,7 @@ import com.jonlatane.beatpad.util.smartrecycler.viewHolders
//import com.jonlatane.beatpad.util.show
import com.jonlatane.beatpad.view.InstrumentConfiguration
import com.jonlatane.beatpad.view.melody.MelodyViewModel
import com.jonlatane.beatpad.view.palette.BeatScratchToolbar
import com.jonlatane.beatpad.view.palette.PaletteUI
import com.jonlatane.beatpad.view.palette.PaletteViewModel
import com.jonlatane.beatpad.view.palette.PartHolder
Expand All @@ -37,10 +41,13 @@ class PaletteEditorActivity : Activity(), Storage, AnkoLogger, InstrumentConfigu
override val storageContext: Context get() = this
private lateinit var ui: PaletteUI
override val viewModel get() = ui.viewModel
companion object {
val RC_SIGN_IN = 1001
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ui = PaletteUI(viewModel = PaletteViewModel(storageContext)).also {
ui = PaletteUI(viewModel = PaletteViewModel(storageContext, this)).also {
it.setContentView(this)
}

Expand Down Expand Up @@ -177,7 +184,7 @@ class PaletteEditorActivity : Activity(), Storage, AnkoLogger, InstrumentConfigu
if (savedInstanceState.getBoolean("melodyOpen", false)) {
viewModel.melodyView.post {
viewModel.backStack.push {
if (viewModel.melodyViewVisible) {
if (viewModel.melodyViewVisible && viewModel.isInEditMode) {
viewModel.melodyViewVisible = false
true
} else false
Expand All @@ -204,12 +211,56 @@ class PaletteEditorActivity : Activity(), Storage, AnkoLogger, InstrumentConfigu
outState.putBoolean("keyboardOpen", !viewModel.keyboardView.isHidden)
outState.putBoolean("colorboardOpen", !viewModel.colorboardView.isHidden)
outState.putBoolean("orbifoldOpen", !viewModel.orbifold.isHidden)
outState.putBoolean("melodyOpen", viewModel.melodyViewModel.melodyView.translationX == 0f)
outState.putBoolean("melodyOpen", viewModel.melodyViewVisible)
outState.putString("editingMelodyId", viewModel.editingMelody?.id.toString())
outState.putInt("beatWidth", viewModel.melodyBeatAdapter.elementWidth)
outState.putInt("beatHeight", viewModel.melodyBeatAdapter.elementHeight)
}

fun signIn() {
// Choose authentication providers
val providers = arrayListOf(
AuthUI.IdpConfig.EmailBuilder().build(),
AuthUI.IdpConfig.PhoneBuilder().build(),
AuthUI.IdpConfig.GoogleBuilder().build())

// Create and launch sign-in intent
startActivityForResult(
AuthUI.getInstance()
.createSignInIntentBuilder()
.setAvailableProviders(providers)
.build(),
RC_SIGN_IN)
}

fun signOut() {
AuthUI.getInstance()
.signOut(this)
.addOnCompleteListener {
viewModel.beatScratchToolbar.updateAppMenu()
}
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)

if (requestCode == RC_SIGN_IN) {
val response = IdpResponse.fromResultIntent(data)

if (resultCode == Activity.RESULT_OK) {
// Successfully signed in
val user = FirebaseAuth.getInstance().currentUser
viewModel.beatScratchToolbar.updateAppMenu()
// ...
} else {
// Sign in failed. If response is null the user canceled the
// sign-in flow using the back button. Otherwise check
// response.getError().getErrorCode() and handle the error.
// ...
}
}
}


/**
* For handling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ object AndroidMidi : AnkoLogger {
)
}
fun send(bytes: ByteArray) {
info("MIDI send: $bytes")
if(sendToInternalSynth) {
ONBOARD_DRIVER.write(bytes)
}
Expand Down
129 changes: 64 additions & 65 deletions app/src/main/kotlin/com/jonlatane/beatpad/midi/MidiControllers.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.jonlatane.beatpad.midi

import android.media.midi.MidiDevice
import android.media.midi.MidiDeviceInfo
import android.media.midi.MidiOutputPort
import android.media.midi.MidiReceiver
import android.os.Build
import android.support.annotation.RequiresApi
import android.view.InputDevice
import com.jonlatane.beatpad.MainApplication
import com.jonlatane.beatpad.midi.MidiConstants.leftHalf
import com.jonlatane.beatpad.midi.MidiConstants.leftHalfMatchesAny
import com.jonlatane.beatpad.midi.MidiConstants.rightHalf
import com.jonlatane.beatpad.midi.MidiDevices.name
import com.jonlatane.beatpad.util.hexString
//import kotlinx.coroutines.experimental.*
Expand All @@ -15,76 +19,71 @@ import kotlin.experimental.and

object MidiControllers: AnkoLogger {
// ..and receiving input via devices' output ports
val receivers: MutableList<(InputDevice, ByteArray) -> Unit> = mutableListOf()
private val inputDevices = mutableMapOf<MidiDeviceInfo, MidiOutputPort>()
val controllers get() = inputDevices.keys
// val receivers: MutableList<(InputDevice, ByteArray) -> Unit> = mutableListOf()
// private val inputDevices = mutableMapOf<MidiDeviceInfo, MidiOutputPort>()
// val controllers get() = inputDevices.keys

@RequiresApi(Build.VERSION_CODES.M)
internal fun setupController(deviceInfo: MidiDeviceInfo) = with(MidiConstants) {
val portNumber = deviceInfo.ports.find {
internal fun setupController(info: MidiDeviceInfo, device: MidiDevice): MidiOutputPort? = with(MidiConstants) {
val portNumber = info.ports.find {
it.type == MidiDeviceInfo.PortInfo.TYPE_OUTPUT
}!!.portNumber
MidiDevices.manager.openDevice(deviceInfo, { device ->
device?.openOutputPort(portNumber)?.let { outputPort ->
MainApplication.instance.toast("Connected to ${deviceInfo.name} output!")
inputDevices[deviceInfo] = outputPort
outputPort.connect(object : MidiReceiver() {
override fun onSend(msg: ByteArray, offset: Int, count: Int, timestamp: Long) {
//info("MIDI data: ${msg.hexString(offset, count)}")
var byteIndex = offset
do {
when {
msg[byteIndex] == TICK -> {
//info("Received beatclock tick ${BeatClockPaletteConsumer.tickPosition}")
if(AndroidMidi.isPlayingFromExternalDevice) {
//BeatClockPaletteConsumer.tickPosition++
//doAsync {
BeatClockPaletteConsumer.tick()
//}
}
}
msg[byteIndex] == PLAY -> {
//info("Received play")
BeatClockPaletteConsumer.tickPosition = 0
AndroidMidi.isPlayingFromExternalDevice = true
}
msg[byteIndex] == STOP -> {
//info("Received stop")
AndroidMidi.isPlayingFromExternalDevice = false
BeatClockPaletteConsumer.tickPosition = 0
}
msg[byteIndex] == SYNC -> {
//info("Received sync")
AndroidMidi.lastMidiSyncTime = System.currentTimeMillis()
}
msg[byteIndex].leftHalfMatchesAny(NOTE_ON, NOTE_OFF) -> {
//info("Received note on")
val noteOnOrOff = msg[byteIndex].leftHalf
val channel = msg[byteIndex].rightHalf
val midiTone = msg[++byteIndex]
val velocity = msg[++byteIndex]
BeatClockPaletteConsumer.palette?.keyboardPart?.instrument?.let { instrument ->
when(noteOnOrOff) {
NOTE_ON -> { instrument.play(midiTone.toInt() - 60, velocity.toInt()) }
NOTE_OFF -> { instrument.stop(midiTone.toInt() - 60) }
}
AndroidMidi.flushSendStream()
}
}
else -> {
error("Unable to parse MIDI: ${msg.hexString(offset, count)}@byte ${byteIndex - offset}")
return
}
}
} while(++byteIndex < offset + count)
}
})
}
}, MidiDevices.handler)
device.openOutputPort(portNumber)?.let { outputPort ->
MainApplication.instance.toast("Controller ${info.name} connected!")
outputPort.connect(Receiver())
outputPort
}
}

@RequiresApi(Build.VERSION_CODES.M)
internal fun destroyController(info: MidiDeviceInfo) {
inputDevices.remove(info)
class Receiver : MidiReceiver() {
override fun onSend(msg: ByteArray, offset: Int, count: Int, timestamp: Long) {
//info("MIDI data: ${msg.hexString(offset, count)}")
var byteIndex = offset
do {
when {
msg[byteIndex] == MidiConstants.TICK -> {
//info("Received beatclock tick ${BeatClockPaletteConsumer.tickPosition}")
if(AndroidMidi.isPlayingFromExternalDevice) {
//BeatClockPaletteConsumer.tickPosition++
//doAsync {
BeatClockPaletteConsumer.tick()
//}
}
}
msg[byteIndex] == MidiConstants.PLAY -> {
//info("Received play")
BeatClockPaletteConsumer.tickPosition = 0
AndroidMidi.isPlayingFromExternalDevice = true
}
msg[byteIndex] == MidiConstants.STOP -> {
//info("Received stop")
AndroidMidi.isPlayingFromExternalDevice = false
BeatClockPaletteConsumer.tickPosition = 0
}
msg[byteIndex] == MidiConstants.SYNC -> {
//info("Received sync")
AndroidMidi.lastMidiSyncTime = System.currentTimeMillis()
}
msg[byteIndex].leftHalfMatchesAny(MidiConstants.NOTE_ON, MidiConstants.NOTE_OFF) -> {
//info("Received note on")
val noteOnOrOff = msg[byteIndex].leftHalf
val channel = msg[byteIndex].rightHalf
val midiTone = msg[++byteIndex]
val velocity = msg[++byteIndex]
BeatClockPaletteConsumer.palette?.keyboardPart?.instrument?.let { instrument ->
when(noteOnOrOff) {
MidiConstants.NOTE_ON -> { instrument.play(midiTone.toInt() - 60, velocity.toInt()) }
MidiConstants.NOTE_OFF -> { instrument.stop(midiTone.toInt() - 60) }
}
AndroidMidi.flushSendStream()
}
}
else -> {
error("Unable to parse MIDI: ${msg.hexString(offset, count)}@byte ${byteIndex - offset}")
return
}
}
} while(++byteIndex < offset + count)
}
}
}
42 changes: 31 additions & 11 deletions app/src/main/kotlin/com/jonlatane/beatpad/midi/MidiDevices.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
package com.jonlatane.beatpad.midi

import BeatClockPaletteConsumer
import android.content.Context
import android.content.pm.PackageManager
import android.media.midi.*
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
import android.support.annotation.RequiresApi
import com.jonlatane.beatpad.MainApplication
import android.os.HandlerThread
import com.jonlatane.beatpad.output.instrument.MIDIInstrument
import org.jetbrains.anko.*
import kotlinx.io.core.Closeable
import org.jetbrains.anko.AnkoLogger
import org.jetbrains.anko.toast

object MidiDevices : AnkoLogger {
data class BSMidiDevice(
val info: MidiDeviceInfo,
val device: MidiDevice,
val inputPort: MidiInputPort?,
val outputPort: MidiOutputPort?
): Closeable {
override fun close() {
device.close()
inputPort?.close()
outputPort?.close()
}
}
val devices = mutableListOf<BSMidiDevice>()

@get:RequiresApi(Build.VERSION_CODES.M)
internal val manager: MidiManager by lazy {
Expand Down Expand Up @@ -54,8 +70,8 @@ object MidiDevices : AnkoLogger {
@RequiresApi(Build.VERSION_CODES.M)
override fun onDeviceRemoved(info: MidiDeviceInfo) {
context.toast("Disconnected from ${info.name}.")
MidiSynthesizers.destroySynthesizer(info)
MidiControllers.destroyController(info)
devices.find { it.info == info }?.close()
devices.removeAll { it.info == info }
}

override fun onDeviceStatusChanged(status: MidiDeviceStatus) {}
Expand All @@ -66,13 +82,17 @@ object MidiDevices : AnkoLogger {

@RequiresApi(Build.VERSION_CODES.M)
private fun setupDevice(info: MidiDeviceInfo) {
// Again, kinda weirdly, we'll be using input ports to set up output devices
if (info.inputPortCount > 0) {
MidiSynthesizers.setupSynthesizer(info)
}
if (info.outputPortCount > 0) {
MidiControllers.setupController(info)
}
manager.openDevice(info, { device ->
// Again, kinda weirdly, we'll be using input ports to set up output devices
val inputPort = if (info.inputPortCount > 0) {
MidiSynthesizers.setupSynthesizer(info, device)
} else null
val outputPort = if (info.outputPortCount > 0) {
MidiControllers.setupController(info, device)
} else null
devices += BSMidiDevice(info, device, inputPort, outputPort)
refreshInstruments()
}, handler)
}

@get:RequiresApi(Build.VERSION_CODES.M)
Expand Down
Loading