1
0
Fork 0

Calculate tone lengths based on user preferences

This commit is contained in:
Eoin Mcloughlin 2020-05-13 20:42:05 +01:00
parent cf89b9b4f3
commit 4216961b5b
3 changed files with 63 additions and 82 deletions

5
TODO
View file

@ -1,7 +1,4 @@
Basic:
* Correct tone length calculation
* Settings
* Koch lesson select
* Run a koch lesson
* Koch result analysis screen
@ -12,6 +9,8 @@ Polish:
* Visual styling
* Settings info text
* Extract UI strings into resource file
* Play sample sounds when settings change
* More Settings
Bonus:
* Morse machine

View file

@ -6,9 +6,9 @@ import android.media.AudioTrack
import android.media.MediaDataSource
import android.view.KeyEvent
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.BlockingQueue
import kotlin.concurrent.thread
import kotlin.math.PI
import kotlin.math.min
import kotlin.math.sin
enum class SoundTypes {
@ -23,44 +23,44 @@ fun StringToSoundSequence(s : String) : List<SoundTypes> {
val first = when(s[0]) {
' ' -> listOf(SoundTypes.WORD_SPACE)
'A' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.WORD_SPACE)
'B' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'C' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'D' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'E' -> listOf(SoundTypes.DIT, SoundTypes.WORD_SPACE)
'F' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'G' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'H' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'I' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'J' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.WORD_SPACE)
'K' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.WORD_SPACE)
'L' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'M' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.WORD_SPACE)
'N' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'O' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.WORD_SPACE)
'P' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'Q' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.WORD_SPACE)
'R' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'S' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'T' -> listOf(SoundTypes.DAH, SoundTypes.WORD_SPACE)
'U' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.WORD_SPACE)
'V' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.WORD_SPACE)
'W' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.WORD_SPACE)
'X' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.WORD_SPACE)
'Y' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.WORD_SPACE)
'Z' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'0' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.WORD_SPACE)
'1' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.WORD_SPACE)
'2' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.WORD_SPACE)
'3' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.WORD_SPACE)
'4' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.WORD_SPACE)
'5' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'6' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'7' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'8' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'9' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'.' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.WORD_SPACE)
'?' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_SPACE)
'A' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'B' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'C' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'D' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'E' -> listOf(SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'F' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'G' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'H' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'I' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'J' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'K' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'L' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'M' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'N' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'O' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'P' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'Q' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'R' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'S' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'T' -> listOf(SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'U' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'V' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'W' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'X' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'Y' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'Z' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'0' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'1' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'2' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'3' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'4' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'5' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'6' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'7' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'8' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'9' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
'.' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
'?' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
else -> { listOf() }
}
@ -113,60 +113,42 @@ fun KeycodeToSoundSequence(keycode : Int) : List<SoundTypes> {
}
class DitDahGenerator : MediaDataSource {
constructor() : super()
override fun close() {
}
override fun getSize() : Long {
return 80000;
}
override fun readAt(position: Long, buffer: ByteArray?, offset: Int, size: Int): Int {
if(position > 10000000)
return -1;
/*
for(i in 0..size) {
val xt = sin((i + offset) / 44e3f) * 255.0f;
buffer?.set(i + offset, xt.toByte());
}
*/
return size;
}
}
class DitDahGeneratorSettings
{
var toneFrequency = 650 //TODO These values are duplicated in the settings fragment
var WordsPerMinute = 20;
//TODO These values are duplicated in the settings fragment
var toneFrequency = 650
var wordsPerMinute = 20
var farnsworthWordsPerMinute = 20
}
class DitDahSoundStream {
constructor(config : DitDahGeneratorSettings) {
// Farnsworth timing calculations: https://morsecode.world/international/timing.html
//TODO: Compute these properly
val ditLengthSeconds = 0.15f;
val dahLengthSeconds = ditLengthSeconds * 3.0f;
val ditLengthInSamples = (ditLengthSeconds * mAudioSampleRate).toInt();
val dahLengthInSamples = (dahLengthSeconds * mAudioSampleRate).toInt();
val interCharacterSpacingInSamples = ditLengthInSamples;
val t_dit = 60.0f / (50.0f * config.wordsPerMinute)
val t_fdit = ((60.0f / min(config.farnsworthWordsPerMinute, config.wordsPerMinute)) - 31.0f * t_dit) / 19.0f
mDitSound = ShortArray(ditLengthInSamples + interCharacterSpacingInSamples);
mDahSound = ShortArray(dahLengthInSamples + interCharacterSpacingInSamples);
mWordSpacingSound = ShortArray((dahLengthSeconds * mAudioSampleRate).toInt());
mCharacterSpacingSound = ShortArray((ditLengthSeconds * mAudioSampleRate).toInt());
val ditLengthInSamples = (t_dit * mAudioSampleRate).toInt()
val dahLengthInSamples = (t_dit * 3 * mAudioSampleRate).toInt()
val intraCharacterSpacingInSamples = (t_dit * mAudioSampleRate).toInt()
val interCharacterSpacingInSamples = (3 * t_fdit * mAudioSampleRate).toInt()
val wordSpacingInSamples = (7 * t_fdit * mAudioSampleRate).toInt()
mDitSound = ShortArray(ditLengthInSamples + intraCharacterSpacingInSamples)
mDahSound = ShortArray(dahLengthInSamples + intraCharacterSpacingInSamples)
// These two subtract the intra-character spacing, because they'll already have been played by the most recent sound
mWordSpacingSound = ShortArray(wordSpacingInSamples - intraCharacterSpacingInSamples);
mCharacterSpacingSound = ShortArray(interCharacterSpacingInSamples - intraCharacterSpacingInSamples);
//TODO: Ramp up? Make sound nicer? Seems to have clipping - just in the emulator?
val invSampleRate = 1.0 / mAudioSampleRate.toFloat()
for(i in 0 until ditLengthInSamples) {
mDitSound[i] = (0x7fff.toFloat() * sin( 2.0f * PI * i * config.toneFrequency * invSampleRate)).toShort();
mDitSound[i] = (0x7fff.toFloat() * sin( 2.0f * PI * i * config.toneFrequency * invSampleRate)).toShort()
}
for(i in 0 until dahLengthInSamples) {
mDahSound[i] = (0x7fff.toFloat() * sin(2.0f * PI * i * config.toneFrequency * invSampleRate) ).toShort();
mDahSound[i] = (0x7fff.toFloat() * sin(2.0f * PI * i * config.toneFrequency * invSampleRate) ).toShort()
}
thread() { makeSoundsWorkerThreadFunc() }

View file

@ -14,11 +14,11 @@
android:orientation="vertical">
<Button
android:id="@+id/Sounder"
android:id="@+id/sounder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="openSounder"
android:text="Button" />
android:text="Sounder" />
<Button
android:id="@+id/button2"