1
0
Fork 0

Add a "finished" callback to the sound generator

This commit is contained in:
Eoin Mcloughlin 2020-05-16 12:05:11 +01:00
parent 4da92cafe0
commit 137f9aebfa
4 changed files with 68 additions and 21 deletions

3
TODO
View file

@ -1,6 +1,5 @@
Basic:
* Koch lesson select
* Run a koch lesson
* Record inputs for koch lesson
* Koch result analysis screen
* Remember last lesson

View file

@ -1,5 +1,6 @@
package es.eoinrul.ecwt
import android.content.SharedPreferences
import android.media.AudioAttributes
import android.media.AudioFormat
import android.media.AudioTrack
@ -337,13 +338,25 @@ fun KeycodeToSoundSequence(keycode : Int) : List<SoundTypes> {
class DitDahGeneratorSettings
{
fun initFromPreferences(sharedPreferences : SharedPreferences) {
//TODO Fix these hardcoded strings
toneFrequency = sharedPreferences.getInt("sender_tone", toneFrequency)
wordsPerMinute = sharedPreferences.getInt("sender_wpn", wordsPerMinute)
farnsworthWordsPerMinute = sharedPreferences.getInt("sender_effectivewpm", farnsworthWordsPerMinute)
}
//TODO These values are duplicated in the settings fragment
var toneFrequency = 650
var wordsPerMinute = 20
var farnsworthWordsPerMinute = 20
}
class DitDahSoundStream {
class DitDahSoundStream : AudioTrack.OnPlaybackPositionUpdateListener {
interface StreamNotificationListener {
fun streamFinished(stream : DitDahSoundStream)
}
constructor(config : DitDahGeneratorSettings) {
// Farnsworth timing calculations: https://morsecode.world/international/timing.html
@ -373,6 +386,11 @@ class DitDahSoundStream {
mDahSound[i] = (0x7fff.toFloat() * sin(2.0f * PI * i * config.toneFrequency * invSampleRate) ).toShort()
}
// Listen for notifications so we can fire a "sound completed" callback, if registered
mSoundPlayer.setPlaybackPositionUpdateListener(this)
// Create a thread that will pull symbols off our queue and write samples to the audio
// device, so it doesn't matter if the write is blocked.
thread() { makeSoundsWorkerThreadFunc() }
}
@ -393,28 +411,47 @@ class DitDahSoundStream {
if(mShouldQuit)
return;
when (sym) {
SoundTypes.DIT -> mSoundPlayer.write(mDitSound, 0, mDitSound.size)
SoundTypes.DAH -> mSoundPlayer.write(mDahSound, 0, mDahSound.size)
SoundTypes.LETTER_SPACE -> mSoundPlayer.write( mCharacterSpacingSound, 0, mCharacterSpacingSound.size)
SoundTypes.WORD_SPACE -> mSoundPlayer.write( mWordSpacingSound, 0, mWordSpacingSound.size)
val soundToWrite = when (sym) {
SoundTypes.DIT -> mDitSound
SoundTypes.DAH -> mDahSound
SoundTypes.LETTER_SPACE -> mCharacterSpacingSound
SoundTypes.WORD_SPACE -> mWordSpacingSound
}
mSoundPlayer.write(soundToWrite, 0, soundToWrite.size)
// Keep track of the number of samples we wrote to fire our "finished" listener
// (Seems "frames" and "samples" are interchangeable in the API?)
mNumberFramesWritten += soundToWrite.size
mSoundPlayer.setNotificationMarkerPosition(mNumberFramesWritten)
// If this is the first symbol, we'll need to start playing.
if (mSoundPlayer.playState != AudioTrack.PLAYSTATE_PLAYING)
mSoundPlayer.play()
}
}
// Optional listener for stream finished event
var streamNotificationListener : StreamNotificationListener? = null
// Used to notify our audio-track-writing thread that it should quit
private var mShouldQuit = false;
// A queue of sounds we want to play
private var mSymbolQueue = ArrayBlockingQueue<SoundTypes>(1000);
private val mAudioSampleRate = 44100;
// Keep track of the amount of data we've written
private var mNumberFramesWritten = 0
// Buffers containing the sounds we want to play
private val mDitSound : ShortArray;
private val mDahSound : ShortArray;
private val mWordSpacingSound : ShortArray;
private val mCharacterSpacingSound : ShortArray;
// Our actual audio track
private val mSoundPlayer : AudioTrack = AudioTrack.Builder()
.setAudioAttributes(
AudioAttributes.Builder()
@ -431,4 +468,11 @@ class DitDahSoundStream {
)
.setBufferSizeInBytes(mAudioSampleRate)
.build();
override fun onMarkerReached(track: AudioTrack?) {
streamNotificationListener?.streamFinished(this)
}
override fun onPeriodicNotification(track: AudioTrack?) {
}
}

View file

@ -29,10 +29,6 @@ class SounderActivity : AppCompatActivity() {
initSoundPlayer()
}
fun startTraining(view: View) {
}
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
val sequence = KeycodeToSoundSequence(keyCode);
@ -49,7 +45,6 @@ class SounderActivity : AppCompatActivity() {
mSoundPlayer = null
}
private fun sequenceToString(sequence: List<SoundTypes>) : String {
var ret = String()
for(symbol in sequence) {
@ -64,10 +59,9 @@ class SounderActivity : AppCompatActivity() {
}
private fun initSoundPlayer() {
val generatorSettings = DitDahGeneratorSettings()
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
generatorSettings.toneFrequency = sharedPreferences.getInt("sender_tone", generatorSettings.toneFrequency)
val generatorSettings = DitDahGeneratorSettings()
generatorSettings.initFromPreferences(sharedPreferences)
mSoundPlayer = DitDahSoundStream(generatorSettings)
}

View file

@ -2,10 +2,12 @@ package es.eoinrul.ecwt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.preference.PreferenceManager
import kotlin.random.Random
class TrainingActivity : AppCompatActivity() {
class TrainingActivity : AppCompatActivity(),
DitDahSoundStream.StreamNotificationListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -18,19 +20,25 @@ class TrainingActivity : AppCompatActivity() {
}
private fun initSoundPlayer() {
val generatorSettings = DitDahGeneratorSettings()
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
generatorSettings.toneFrequency = sharedPreferences.getInt("sender_tone", generatorSettings.toneFrequency)
val generatorSettings = DitDahGeneratorSettings()
generatorSettings.initFromPreferences(sharedPreferences)
mSoundPlayer = DitDahSoundStream(generatorSettings)
mSoundPlayer.streamNotificationListener = this
}
private fun startLesson(alphabet : String) {
val generatorSettings = DitDahGeneratorSettings()
val lessonLengthInMinutes = 1 // TODO These should be configurable from the settings window
val wordSize = 5
val numberOfWords = generatorSettings.farnsworthWordsPerMinute * lessonLengthInMinutes
var sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this)
val quickTestingSwitchEnabled = sharedPrefs.getBoolean("switch_preference_1", false)
val numberOfWords = if(!quickTestingSwitchEnabled) {
generatorSettings.farnsworthWordsPerMinute * lessonLengthInMinutes
} else { 2 } // This is just for quicker testing; remove eventually
var lessonText = String()
var rng = Random.Default
@ -47,5 +55,7 @@ class TrainingActivity : AppCompatActivity() {
}
private lateinit var mSoundPlayer : DitDahSoundStream
override fun streamFinished(stream: DitDahSoundStream) {
}
}