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

View file

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

View file

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