Calculate tone lengths based on user preferences
This commit is contained in:
parent
cf89b9b4f3
commit
4216961b5b
3 changed files with 63 additions and 82 deletions
5
TODO
5
TODO
|
@ -1,7 +1,4 @@
|
||||||
Basic:
|
Basic:
|
||||||
* Correct tone length calculation
|
|
||||||
* Settings
|
|
||||||
|
|
||||||
* Koch lesson select
|
* Koch lesson select
|
||||||
* Run a koch lesson
|
* Run a koch lesson
|
||||||
* Koch result analysis screen
|
* Koch result analysis screen
|
||||||
|
@ -12,6 +9,8 @@ Polish:
|
||||||
* Visual styling
|
* Visual styling
|
||||||
* Settings info text
|
* Settings info text
|
||||||
* Extract UI strings into resource file
|
* Extract UI strings into resource file
|
||||||
|
* Play sample sounds when settings change
|
||||||
|
* More Settings
|
||||||
|
|
||||||
Bonus:
|
Bonus:
|
||||||
* Morse machine
|
* Morse machine
|
||||||
|
|
|
@ -6,9 +6,9 @@ import android.media.AudioTrack
|
||||||
import android.media.MediaDataSource
|
import android.media.MediaDataSource
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import java.util.concurrent.ArrayBlockingQueue
|
import java.util.concurrent.ArrayBlockingQueue
|
||||||
import java.util.concurrent.BlockingQueue
|
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.min
|
||||||
import kotlin.math.sin
|
import kotlin.math.sin
|
||||||
|
|
||||||
enum class SoundTypes {
|
enum class SoundTypes {
|
||||||
|
@ -23,44 +23,44 @@ fun StringToSoundSequence(s : String) : List<SoundTypes> {
|
||||||
|
|
||||||
val first = when(s[0]) {
|
val first = when(s[0]) {
|
||||||
' ' -> listOf(SoundTypes.WORD_SPACE)
|
' ' -> listOf(SoundTypes.WORD_SPACE)
|
||||||
'A' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.WORD_SPACE)
|
'A' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
|
||||||
'B' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_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.WORD_SPACE)
|
'C' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
|
||||||
'D' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_SPACE)
|
'D' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
|
||||||
'E' -> listOf(SoundTypes.DIT, SoundTypes.WORD_SPACE)
|
'E' -> listOf(SoundTypes.DIT, SoundTypes.LETTER_SPACE)
|
||||||
'F' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.WORD_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.WORD_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.WORD_SPACE)
|
'H' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
|
||||||
'I' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_SPACE)
|
'I' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
|
||||||
'J' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.WORD_SPACE)
|
'J' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
|
||||||
'K' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.WORD_SPACE)
|
'K' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
|
||||||
'L' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_SPACE)
|
'L' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
|
||||||
'M' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.WORD_SPACE)
|
'M' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
|
||||||
'N' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.WORD_SPACE)
|
'N' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
|
||||||
'O' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.WORD_SPACE)
|
'O' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
|
||||||
'P' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.WORD_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.WORD_SPACE)
|
'Q' -> listOf(SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
|
||||||
'R' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.WORD_SPACE)
|
'R' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
|
||||||
'S' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.WORD_SPACE)
|
'S' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
|
||||||
'T' -> listOf(SoundTypes.DAH, SoundTypes.WORD_SPACE)
|
'T' -> listOf(SoundTypes.DAH, SoundTypes.LETTER_SPACE)
|
||||||
'U' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.WORD_SPACE)
|
'U' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
|
||||||
'V' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.WORD_SPACE)
|
'V' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
|
||||||
'W' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.WORD_SPACE)
|
'W' -> listOf(SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.LETTER_SPACE)
|
||||||
'X' -> listOf(SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.WORD_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.WORD_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.WORD_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.WORD_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.WORD_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.WORD_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.WORD_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.WORD_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.WORD_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.WORD_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.WORD_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.WORD_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.WORD_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.WORD_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.WORD_SPACE)
|
'?' -> listOf(SoundTypes.DIT, SoundTypes.DIT, SoundTypes.DAH, SoundTypes.DAH, SoundTypes.DIT, SoundTypes.DIT, SoundTypes.LETTER_SPACE)
|
||||||
else -> { listOf() }
|
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
|
class DitDahGeneratorSettings
|
||||||
{
|
{
|
||||||
var toneFrequency = 650 //TODO These values are duplicated in the settings fragment
|
//TODO These values are duplicated in the settings fragment
|
||||||
var WordsPerMinute = 20;
|
var toneFrequency = 650
|
||||||
|
var wordsPerMinute = 20
|
||||||
|
var farnsworthWordsPerMinute = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
class DitDahSoundStream {
|
class DitDahSoundStream {
|
||||||
constructor(config : DitDahGeneratorSettings) {
|
constructor(config : DitDahGeneratorSettings) {
|
||||||
// Farnsworth timing calculations: https://morsecode.world/international/timing.html
|
// 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 t_dit = 60.0f / (50.0f * config.wordsPerMinute)
|
||||||
val dahLengthInSamples = (dahLengthSeconds * mAudioSampleRate).toInt();
|
val t_fdit = ((60.0f / min(config.farnsworthWordsPerMinute, config.wordsPerMinute)) - 31.0f * t_dit) / 19.0f
|
||||||
val interCharacterSpacingInSamples = ditLengthInSamples;
|
|
||||||
|
|
||||||
mDitSound = ShortArray(ditLengthInSamples + interCharacterSpacingInSamples);
|
val ditLengthInSamples = (t_dit * mAudioSampleRate).toInt()
|
||||||
mDahSound = ShortArray(dahLengthInSamples + interCharacterSpacingInSamples);
|
val dahLengthInSamples = (t_dit * 3 * mAudioSampleRate).toInt()
|
||||||
mWordSpacingSound = ShortArray((dahLengthSeconds * mAudioSampleRate).toInt());
|
val intraCharacterSpacingInSamples = (t_dit * mAudioSampleRate).toInt()
|
||||||
mCharacterSpacingSound = ShortArray((ditLengthSeconds * 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?
|
//TODO: Ramp up? Make sound nicer? Seems to have clipping - just in the emulator?
|
||||||
val invSampleRate = 1.0 / mAudioSampleRate.toFloat()
|
val invSampleRate = 1.0 / mAudioSampleRate.toFloat()
|
||||||
for(i in 0 until ditLengthInSamples) {
|
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) {
|
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() }
|
thread() { makeSoundsWorkerThreadFunc() }
|
||||||
|
|
|
@ -14,11 +14,11 @@
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/Sounder"
|
android:id="@+id/sounder"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:onClick="openSounder"
|
android:onClick="openSounder"
|
||||||
android:text="Button" />
|
android:text="Sounder" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/button2"
|
android:id="@+id/button2"
|
||||||
|
|
Loading…
Reference in a new issue