Change sounder UI and input handling
Let user see the text box that's being entered; pop items off one at a time and display in a history view. This might display the first few symbols faster than they're being heard, depending on the size of the audio buffers. Not sure what to do about that, other than writing a real-time mixer.
This commit is contained in:
parent
c6b9a8b266
commit
2cdc7d75cd
4 changed files with 86 additions and 99 deletions
|
@ -312,53 +312,6 @@ fun SequenceToString(sequence: List<SoundTypes>) : String {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// Almost certainly the wrong way to do this
|
|
||||||
fun KeycodeToSoundSequence(keycode : Int) : List<SoundTypes> {
|
|
||||||
return when(keycode) {
|
|
||||||
KeyEvent.KEYCODE_A -> StringToSoundSequence("A");
|
|
||||||
KeyEvent.KEYCODE_B -> StringToSoundSequence("B");
|
|
||||||
KeyEvent.KEYCODE_C -> StringToSoundSequence("C");
|
|
||||||
KeyEvent.KEYCODE_D -> StringToSoundSequence("D");
|
|
||||||
KeyEvent.KEYCODE_E -> StringToSoundSequence("E");
|
|
||||||
KeyEvent.KEYCODE_F -> StringToSoundSequence("F");
|
|
||||||
KeyEvent.KEYCODE_G -> StringToSoundSequence("G");
|
|
||||||
KeyEvent.KEYCODE_H -> StringToSoundSequence("H");
|
|
||||||
KeyEvent.KEYCODE_I -> StringToSoundSequence("I");
|
|
||||||
KeyEvent.KEYCODE_J -> StringToSoundSequence("J");
|
|
||||||
KeyEvent.KEYCODE_K -> StringToSoundSequence("K");
|
|
||||||
KeyEvent.KEYCODE_L -> StringToSoundSequence("L");
|
|
||||||
KeyEvent.KEYCODE_M -> StringToSoundSequence("M");
|
|
||||||
KeyEvent.KEYCODE_N -> StringToSoundSequence("N");
|
|
||||||
KeyEvent.KEYCODE_O -> StringToSoundSequence("O");
|
|
||||||
KeyEvent.KEYCODE_P -> StringToSoundSequence("P");
|
|
||||||
KeyEvent.KEYCODE_Q -> StringToSoundSequence("Q");
|
|
||||||
KeyEvent.KEYCODE_R -> StringToSoundSequence("R");
|
|
||||||
KeyEvent.KEYCODE_S -> StringToSoundSequence("S");
|
|
||||||
KeyEvent.KEYCODE_T -> StringToSoundSequence("T");
|
|
||||||
KeyEvent.KEYCODE_U -> StringToSoundSequence("U");
|
|
||||||
KeyEvent.KEYCODE_V -> StringToSoundSequence("V");
|
|
||||||
KeyEvent.KEYCODE_W -> StringToSoundSequence("W");
|
|
||||||
KeyEvent.KEYCODE_X -> StringToSoundSequence("X");
|
|
||||||
KeyEvent.KEYCODE_Y -> StringToSoundSequence("Y");
|
|
||||||
KeyEvent.KEYCODE_Z -> StringToSoundSequence("Z");
|
|
||||||
KeyEvent.KEYCODE_0 -> StringToSoundSequence("0");
|
|
||||||
KeyEvent.KEYCODE_1 -> StringToSoundSequence("1");
|
|
||||||
KeyEvent.KEYCODE_2 -> StringToSoundSequence("2");
|
|
||||||
KeyEvent.KEYCODE_3 -> StringToSoundSequence("3");
|
|
||||||
KeyEvent.KEYCODE_4 -> StringToSoundSequence("4");
|
|
||||||
KeyEvent.KEYCODE_5 -> StringToSoundSequence("5");
|
|
||||||
KeyEvent.KEYCODE_6 -> StringToSoundSequence("6");
|
|
||||||
KeyEvent.KEYCODE_7 -> StringToSoundSequence("7");
|
|
||||||
KeyEvent.KEYCODE_8 -> StringToSoundSequence("8");
|
|
||||||
KeyEvent.KEYCODE_9 -> StringToSoundSequence("9");
|
|
||||||
KeyEvent.KEYCODE_PERIOD -> StringToSoundSequence(".");
|
|
||||||
//KeyEvent.KEYCODE_S -> StringToSoundSequence("?");
|
|
||||||
else -> {
|
|
||||||
StringToSoundSequence(" ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class DitDahGeneratorSettings(var context : Context? = null) {
|
data class DitDahGeneratorSettings(var context : Context? = null) {
|
||||||
//TODO These values are duplicated in the settings fragment
|
//TODO These values are duplicated in the settings fragment
|
||||||
var toneFrequency = 650
|
var toneFrequency = 650
|
||||||
|
|
|
@ -1,45 +1,31 @@
|
||||||
package es.eoinrul.ecwt
|
package es.eoinrul.ecwt
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.media.MediaPlayer
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.view.inputmethod.InputMethodManager
|
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import java.util.*
|
import kotlin.math.max
|
||||||
|
|
||||||
|
|
||||||
// Activity that only echos any inputs typed on a keyboard
|
// Activity that only echos any inputs typed on a keyboard
|
||||||
class SounderActivity : AppCompatActivity() {
|
class SounderActivity : AppCompatActivity(), DitDahSoundStream.StreamNotificationListener {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_sounder)
|
setContentView(R.layout.activity_sounder)
|
||||||
|
|
||||||
//val sounderMode = intent.getStringExtra(SOUNDER_MODE);
|
//val sounderMode = intent.getStringExtra(SOUNDER_MODE);
|
||||||
|
mPreviouslySentText = findViewById<TextView>(R.id.sentText)
|
||||||
mTextViewTest = findViewById<TextView>(R.id.keyedText);
|
mCurrentlySendingText = findViewById<TextView>(R.id.keyedText)
|
||||||
|
|
||||||
// This is a hidden EditText that's used to bring up the soft keyboard:
|
// This is a hidden EditText that's used to bring up the soft keyboard:
|
||||||
mKeyboardInput = findViewById<EditText>(R.id.sounderInput);
|
mKeyboardInput = findViewById<EditText>(R.id.sounderInput)
|
||||||
mKeyboardInput?.addTextChangedListener(mInputHandler)
|
mKeyboardInput?.addTextChangedListener(mInputHandler)
|
||||||
mKeyboardInput?.requestFocus();
|
mKeyboardInput?.requestFocus()
|
||||||
|
|
||||||
initSoundPlayer()
|
initSoundPlayer()
|
||||||
onTextEntered("E"); // Display and sound something - this is arbitrary
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will be called for connected USB/Bluetooth keyboards:
|
|
||||||
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
|
|
||||||
val sequence = KeycodeToSoundSequence(keyCode)
|
|
||||||
mTextViewTest?.text = SequenceToString(sequence)
|
|
||||||
mSoundPlayer?.enqueue(sequence)
|
|
||||||
mKeyboardInput?.setText("") // Should be empty due to physical keyboard
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
@ -48,7 +34,7 @@ class SounderActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause();
|
super.onPause()
|
||||||
|
|
||||||
mSoundPlayer?.quit()
|
mSoundPlayer?.quit()
|
||||||
mSoundPlayer = null
|
mSoundPlayer = null
|
||||||
|
@ -57,39 +43,28 @@ class SounderActivity : AppCompatActivity() {
|
||||||
private fun initSoundPlayer() {
|
private fun initSoundPlayer() {
|
||||||
val generatorSettings = DitDahGeneratorSettings(this)
|
val generatorSettings = DitDahGeneratorSettings(this)
|
||||||
mSoundPlayer = DitDahSoundStream(generatorSettings)
|
mSoundPlayer = DitDahSoundStream(generatorSettings)
|
||||||
|
mSoundPlayer!!.streamNotificationListener = this
|
||||||
|
mSpaceDurationMs = (mSoundPlayer!!.durationOf(listOf(SoundTypes.LETTER_SPACE)) * 1000).toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onTextEntered(typedString : String) {
|
private var mPreviouslySentText : TextView? = null
|
||||||
val sequence = StringToSoundSequence(typedString);
|
private var mCurrentlySendingText : TextView? = null
|
||||||
|
private var mKeyboardInput : EditText? = null
|
||||||
|
|
||||||
// Display the text:
|
private var mSoundPlayer : DitDahSoundStream? = null
|
||||||
mTextViewTest?.text = SequenceToString(sequence);
|
// Flag to indicate if the sound player is waiting to send another symbol:
|
||||||
|
private var mSoundAwaitingText : Boolean = true
|
||||||
|
|
||||||
// Then play the sound:
|
// Duration of a space with current sound gen. Used to add spaces
|
||||||
mSoundPlayer?.enqueue(sequence);
|
// automatically, if user input is slow
|
||||||
|
private var mSpaceDurationMs : Long = 0
|
||||||
// Finally, clear the input box, as we don't need it anymore:
|
private var mTimeOfFirstSymbolWait : Long = 0
|
||||||
mKeyboardInput?.setText("");
|
|
||||||
}
|
|
||||||
|
|
||||||
private var mTextViewTest : TextView? = null;
|
|
||||||
private var mKeyboardInput : EditText? = null;
|
|
||||||
private var mMediaPlayer : MediaPlayer? = null;
|
|
||||||
|
|
||||||
private var mSoundPlayer : DitDahSoundStream? = null;
|
|
||||||
|
|
||||||
// Utility to watch our EditText and handle any user input
|
// Utility to watch our EditText and handle any user input
|
||||||
// This only seems to get triggered if we're using the software keyboard
|
// This only seems to get triggered if we're using the software keyboard
|
||||||
private val mInputHandler = object : TextWatcher {
|
private val mInputHandler = object : TextWatcher {
|
||||||
override fun afterTextChanged(s: Editable?) {
|
override fun afterTextChanged(s: Editable?) {
|
||||||
if(s == null) {
|
ensurePlaying()
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(s.isNotEmpty()) {
|
|
||||||
var typedString: String = s.toString().capitalize();
|
|
||||||
onTextEntered(typedString)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
||||||
|
@ -98,4 +73,48 @@ class SounderActivity : AppCompatActivity() {
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ensurePlaying() {
|
||||||
|
if(mSoundAwaitingText && mSoundPlayer != null) {
|
||||||
|
symbolsExhausted(mSoundPlayer!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun symbolsExhausted(stream: DitDahSoundStream) {
|
||||||
|
if (!mSoundAwaitingText) {
|
||||||
|
mTimeOfFirstSymbolWait = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
|
||||||
|
mSoundAwaitingText = true
|
||||||
|
runOnUiThread {
|
||||||
|
if( mKeyboardInput?.text!!.isNotEmpty() && mSoundAwaitingText ) {
|
||||||
|
mSoundAwaitingText = false
|
||||||
|
// Extract and remove the first character
|
||||||
|
val firstInput = mKeyboardInput!!.text!!.subSequence(0, 1).toString()
|
||||||
|
mKeyboardInput!!.text = mKeyboardInput!!.text.replace(0, 1, "")
|
||||||
|
// Move text entry cursor to the end
|
||||||
|
mKeyboardInput!!.setSelection(mKeyboardInput!!.text.length)
|
||||||
|
|
||||||
|
val sequence = StringToSoundSequence(firstInput.capitalize())
|
||||||
|
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val extraSpace = if(now - mTimeOfFirstSymbolWait > mSpaceDurationMs) {
|
||||||
|
" "
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display the text:
|
||||||
|
mCurrentlySendingText?.text = SequenceToString(sequence)
|
||||||
|
val maxHistory = 20
|
||||||
|
val trimFrom = max(0, mPreviouslySentText?.text!!.length - maxHistory
|
||||||
|
- extraSpace.length - firstInput.length)
|
||||||
|
mPreviouslySentText?.text = mPreviouslySentText?.text!!.substring(trimFrom,
|
||||||
|
mPreviouslySentText?.text!!.length) + extraSpace + firstInput
|
||||||
|
|
||||||
|
// Then play the sound:
|
||||||
|
mSoundPlayer?.enqueue(sequence)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,21 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context="es.eoinrul.ecwt.SounderActivity">
|
tools:context="es.eoinrul.ecwt.SounderActivity">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sentText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:ellipsize="start"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textAlignment="viewEnd"
|
||||||
|
android:textSize="36sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/keyedText"
|
android:id="@+id/keyedText"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -14,20 +29,19 @@
|
||||||
android:textSize="36sp"
|
android:textSize="36sp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toBottomOf="@+id/sentText" />
|
||||||
|
|
||||||
<!-- This is a bit of a trick; the most reliable way of bringing up a keyboard
|
|
||||||
in an activity seems to be to create a hidden EditText and give it
|
|
||||||
focus when the activity starts -->
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="gone">
|
android:layout_margin="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/keyedText" >
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/sounderInput"
|
android:id="@+id/sounderInput"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:ems="10"
|
android:ems="10"
|
||||||
|
android:textSize="36sp"
|
||||||
android:inputType="text|textNoSuggestions"
|
android:inputType="text|textNoSuggestions"
|
||||||
tools:layout_editor_absoluteX="99dp"
|
tools:layout_editor_absoluteX="99dp"
|
||||||
tools:layout_editor_absoluteY="53dp" />
|
tools:layout_editor_absoluteY="53dp" />
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
Fixed bug where duration of lessons was too long
|
Fixed bug where duration of lessons was too long
|
||||||
Added tip if users didn't add spaces to lesson input
|
Added tip if users didn't add spaces to lesson input
|
||||||
|
Improved "Sounder" UI. Now shows some history and upcoming text.
|
||||||
|
|
Loading…
Reference in a new issue