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
}
// 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) {
//TODO These values are duplicated in the settings fragment
var toneFrequency = 650

View file

@ -1,45 +1,31 @@
package es.eoinrul.ecwt
import android.content.Context
import android.media.MediaPlayer
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.KeyEvent
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import java.util.*
import kotlin.math.max
// Activity that only echos any inputs typed on a keyboard
class SounderActivity : AppCompatActivity() {
class SounderActivity : AppCompatActivity(), DitDahSoundStream.StreamNotificationListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sounder)
//val sounderMode = intent.getStringExtra(SOUNDER_MODE);
mTextViewTest = findViewById<TextView>(R.id.keyedText);
mPreviouslySentText = findViewById<TextView>(R.id.sentText)
mCurrentlySendingText = findViewById<TextView>(R.id.keyedText)
// 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?.requestFocus();
mKeyboardInput?.requestFocus()
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() {
@ -48,7 +34,7 @@ class SounderActivity : AppCompatActivity() {
}
override fun onPause() {
super.onPause();
super.onPause()
mSoundPlayer?.quit()
mSoundPlayer = null
@ -57,39 +43,28 @@ class SounderActivity : AppCompatActivity() {
private fun initSoundPlayer() {
val generatorSettings = DitDahGeneratorSettings(this)
mSoundPlayer = DitDahSoundStream(generatorSettings)
mSoundPlayer!!.streamNotificationListener = this
mSpaceDurationMs = (mSoundPlayer!!.durationOf(listOf(SoundTypes.LETTER_SPACE)) * 1000).toLong()
}
private fun onTextEntered(typedString : String) {
val sequence = StringToSoundSequence(typedString);
private var mPreviouslySentText : TextView? = null
private var mCurrentlySendingText : TextView? = null
private var mKeyboardInput : EditText? = null
// Display the text:
mTextViewTest?.text = SequenceToString(sequence);
private var mSoundPlayer : DitDahSoundStream? = null
// Flag to indicate if the sound player is waiting to send another symbol:
private var mSoundAwaitingText : Boolean = true
// Then play the sound:
mSoundPlayer?.enqueue(sequence);
// Finally, clear the input box, as we don't need it anymore:
mKeyboardInput?.setText("");
}
private var mTextViewTest : TextView? = null;
private var mKeyboardInput : EditText? = null;
private var mMediaPlayer : MediaPlayer? = null;
private var mSoundPlayer : DitDahSoundStream? = null;
// Duration of a space with current sound gen. Used to add spaces
// automatically, if user input is slow
private var mSpaceDurationMs : Long = 0
private var mTimeOfFirstSymbolWait : Long = 0
// Utility to watch our EditText and handle any user input
// This only seems to get triggered if we're using the software keyboard
private val mInputHandler = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
if(s == null) {
return;
}
if(s.isNotEmpty()) {
var typedString: String = s.toString().capitalize();
onTextEntered(typedString)
}
ensurePlaying()
}
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) {
}
}
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"
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
android:id="@+id/keyedText"
android:layout_width="wrap_content"
@ -14,20 +29,19 @@
android:textSize="36sp"
app:layout_constraintEnd_toEndOf="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
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
android:layout_margin="16dp"
app:layout_constraintTop_toBottomOf="@+id/keyedText" >
<EditText
android:id="@+id/sounderInput"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:textSize="36sp"
android:inputType="text|textNoSuggestions"
tools:layout_editor_absoluteX="99dp"
tools:layout_editor_absoluteY="53dp" />

View file

@ -1,2 +1,3 @@
Fixed bug where duration of lessons was too long
Added tip if users didn't add spaces to lesson input
Improved "Sounder" UI. Now shows some history and upcoming text.