1
0
Fork 0

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:
Eoin Mcloughlin 2022-08-28 13:51:55 +01:00
parent c6b9a8b266
commit 2cdc7d75cd
4 changed files with 86 additions and 99 deletions

View file

@ -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

View file

@ -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)
}
}
}
} }

View file

@ -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" />

View file

@ -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.