Skip to content

Commit 7b1d0e5

Browse files
committed
Public Bands
1 parent 4330546 commit 7b1d0e5

File tree

7 files changed

+433
-285
lines changed

7 files changed

+433
-285
lines changed

AudioCoder/src/main/java/org/operatorfoundation/audiocoder/Bandplan.java

Lines changed: 0 additions & 40 deletions
This file was deleted.

AudioCoder/src/main/java/org/operatorfoundation/audiocoder/DBToXMLConverter.java

Lines changed: 0 additions & 204 deletions
This file was deleted.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package org.operatorfoundation.audiocoder
2+
3+
/**
4+
* WSPR Band Plan containing official WSPR frequencies for amateur radio bands.
5+
*
6+
* These frequencies represent the dial frequencies where WSPR signals are transmitted,
7+
* based on the international WSPR band plan coordination.
8+
*/
9+
data class WSPRBand(
10+
val name: String,
11+
val dialFrequencyMHz: Double,
12+
val wavelengthMeters: String,
13+
val region: ITURegion = ITURegion.GLOBAL,
14+
val isPopular: Boolean = false
15+
)
16+
{
17+
/**
18+
* Gets a user-friendly display name for the band.
19+
*/
20+
override fun toString(): String {
21+
return when (region)
22+
{
23+
ITURegion.GLOBAL -> name
24+
ITURegion.REGION_1 -> "$name (R1)"
25+
ITURegion.REGION_2 -> "$name (R2)"
26+
ITURegion.REGION_3 -> "$name (R3)"
27+
}
28+
}
29+
}
30+
31+
/**
32+
* ITU Regions for amateur radio frequency coordination.
33+
*/
34+
enum class ITURegion
35+
{
36+
GLOBAL, // Used worldwide
37+
REGION_1, // Europe, Africa, Middle East, Asia North of 40°N
38+
REGION_2, // Americas,
39+
REGION_3, // Asia south of 40°N, Oceania
40+
}
41+
42+
/**
43+
* Official WSPR Band Plan
44+
*
45+
* This class provides access to the standardized WSPR frequencies used worldwide.
46+
* Frequencies are based on international amateur radio band plans and WSPR coordination.
47+
*/
48+
object WSPRBandplan
49+
{
50+
/**
51+
* All official WSPR Bands with their dial frequencies.
52+
*/
53+
val ALL_BANDS = listOf(
54+
// LF/MF Bands (requires special authorization in most countries)
55+
WSPRBand(name = "LF", dialFrequencyMHz = 0.136000, wavelengthMeters = "2200m", ITURegion.GLOBAL),
56+
WSPRBand(name = "MF", dialFrequencyMHz = 0.474200, wavelengthMeters = "630m", ITURegion.GLOBAL),
57+
58+
// HF Bands (most popular for WSPR)
59+
WSPRBand(name = "160m", dialFrequencyMHz = 1.836600, wavelengthMeters = "160m", ITURegion.GLOBAL),
60+
WSPRBand(name = "80m", dialFrequencyMHz = 3.568600, wavelengthMeters = "80m", ITURegion.REGION_2),
61+
WSPRBand(name = "80m", dialFrequencyMHz = 3.592600, wavelengthMeters = "80m", ITURegion.REGION_1, isPopular = true),
62+
WSPRBand(name = "60m", dialFrequencyMHz = 5.287200, wavelengthMeters = "60m", ITURegion.REGION_2),
63+
WSPRBand(name = "60m", dialFrequencyMHz = 5.364700, wavelengthMeters = "60m", ITURegion.REGION_1),
64+
WSPRBand(name = "40m", dialFrequencyMHz = 7.038600, wavelengthMeters = "40m", ITURegion.GLOBAL, isPopular = true),
65+
WSPRBand(name = "30m", dialFrequencyMHz = 10.138700, wavelengthMeters = "30m", ITURegion.GLOBAL, isPopular = true),
66+
WSPRBand(name = "20m", dialFrequencyMHz = 14.095600, wavelengthMeters = "20m", ITURegion.GLOBAL, isPopular = true),
67+
WSPRBand(name = "17m", dialFrequencyMHz = 18.104600, wavelengthMeters = "17m", ITURegion.GLOBAL),
68+
WSPRBand(name = "15m", dialFrequencyMHz = 21.094600, wavelengthMeters = "15m", ITURegion.GLOBAL, isPopular = true),
69+
WSPRBand(name = "12m", dialFrequencyMHz = 24.924600, wavelengthMeters = "12m", ITURegion.GLOBAL),
70+
WSPRBand(name = "10m", dialFrequencyMHz = 28.124600, wavelengthMeters = "10m", ITURegion.GLOBAL, isPopular = true),
71+
72+
// VHF/UHF/Microwave Bands
73+
WSPRBand(name = "6m", dialFrequencyMHz = 50.293000, wavelengthMeters = "6m", ITURegion.GLOBAL),
74+
WSPRBand(name = "4m", dialFrequencyMHz = 70.091000, wavelengthMeters = "4m", ITURegion.REGION_1), // Mostly Europe
75+
WSPRBand(name = "2m", dialFrequencyMHz = 144.489000, wavelengthMeters = "2m", ITURegion.GLOBAL),
76+
WSPRBand(name = "70cm", dialFrequencyMHz = 432.300000, wavelengthMeters = "70cm", ITURegion.GLOBAL),
77+
WSPRBand(name = "23cm", dialFrequencyMHz = 1296.500000, wavelengthMeters = "23cm", ITURegion.GLOBAL)
78+
)
79+
80+
/**
81+
* Gets the most popular WSPR bands.
82+
*/
83+
fun getPopularBands(): List<WSPRBand> = ALL_BANDS.filter { it.isPopular }
84+
85+
/**
86+
* Gets WSPR bands for a specific ITU region
87+
*/
88+
fun getBandsForRegion(region: ITURegion): List<WSPRBand> = ALL_BANDS.filter { it.region == region || it.region == ITURegion.GLOBAL }
89+
90+
/**
91+
* Gets all available WSPR frequencies as a map of display name to frequency.
92+
*/
93+
fun getFrequencyMap(): Map<String, Double> = ALL_BANDS.associate { it.toString() to it.dialFrequencyMHz}
94+
95+
/**
96+
* Finds a WSPR band by frequency within a given tolerance (default tolerance is 1kHz).
97+
*/
98+
fun findBandByFrequency(frequencyMHz: Double, toleranceKHz: Double = 1.0): WSPRBand?
99+
{
100+
val toleranceMHz = toleranceKHz / 1000.0
101+
return ALL_BANDS.find {
102+
kotlin.math.abs(it.dialFrequencyMHz - frequencyMHz) <= toleranceMHz
103+
}
104+
}
105+
106+
/**
107+
* Gets the default WSPR frequency (20m)
108+
*/
109+
fun getDefaultFrequency(): Double = ALL_BANDS.find { it.name == "20m" && it.region == ITURegion.GLOBAL }?.dialFrequencyMHz ?: 14.095600
110+
111+
/**
112+
* Validates if a frequency is a valid WSPR frequency
113+
*/
114+
fun isValidWSPRFRequency(frequencyMHz: Double): Boolean = findBandByFrequency(frequencyMHz) != null
115+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.operatorfoundation.audiocoder
2+
3+
object WSPRConstants
4+
{
5+
const val SAMPLE_RATE_HZ = 12000 // WSPR uses 12kHz sample rate
6+
const val CENTER_FREQUENCY_HZ = 1500
7+
const val SYMBOL_LENGTH = 8192
8+
const val SYMBOLS_PER_MESSAGE = 162 // WSPR messages have 162 symbols
9+
}

AudioCoder/src/main/java/org/operatorfoundation/audiocoder/WSPRFileManager.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.content.Context
44
import android.content.Intent
55
import android.icu.text.SimpleDateFormat
66
import androidx.core.content.FileProvider
7+
import org.operatorfoundation.audiocoder.WSPRConstants.SAMPLE_RATE_HZ
78
import timber.log.Timber
89
import java.io.File
910
import java.io.FileOutputStream
@@ -15,7 +16,6 @@ class WSPRFileManager(private val context: Context)
1516
{
1617
companion object
1718
{
18-
private const val SAMPLE_RATE = 12000 // WSPR uses 12kHz
1919
private const val BITS_PER_SAMPLE = 16
2020
private const val CHANNELS = 1
2121
}
@@ -84,8 +84,8 @@ class WSPRFileManager(private val context: Context)
8484
putInt(16) // PCM format chunk size
8585
putShort(1) // PCM format
8686
putShort(CHANNELS.toShort())
87-
putInt(SAMPLE_RATE)
88-
putInt(SAMPLE_RATE * CHANNELS * BITS_PER_SAMPLE / 8) // Byte rate
87+
putInt(SAMPLE_RATE_HZ)
88+
putInt(SAMPLE_RATE_HZ * CHANNELS * BITS_PER_SAMPLE / 8) // Byte rate
8989
putShort((CHANNELS * BITS_PER_SAMPLE / 8).toShort()) // Block align
9090
putShort(BITS_PER_SAMPLE.toShort())
9191

0 commit comments

Comments
 (0)