I’ve been really bored recently. So I’ve been looking to see how many FM stations I could find.

Equipment is AirSpy HF+ Discovery, SDRSharp, and a Discone Antenna mounted ~2m above the ground.

The websites FM Scan and VHF Propagation Map have been very useful for identifying stations, and finding good times to listen.

Furthest station was ABC Classic in Griffith NSW. Bit over 400KM away.

Updated 2021-09-22: Added a couple of new entries, included basic signal quality, and updated code.

Frequency Station Name Signal
87.6 Mhz Surf FM Medium
87.8 Mhz Kiss FM Medium
88 Mhz DCFM 88.0 Low
88.1 Mhz 3MFM Medium
88.3 Mhz Southern FM Medium
88.5 Mhz ABC Northen Tasmania DX
88.6 Mhz Plenty Valley FM Medium
88.7 Mhz Vision Radio Bendigo Very low
88.9 Mhz WynFM Medium
89 Mhz Radio Bayside Low
89.1 Mhz 3MFM Medium
89.3 Mhz Kix Country Radio Medium
89.5 Mhz Very low
89.7 Mhz ABC Radio National Willis Hill Tas DX
89.9 Mhz Light899 High
90.3 Mhz TripleJ Low
90.7 Mhz SYN FM High
90.9 Mhz TripleJ Tasmania DX
91.1 Mhz ABC Central Victoria Low
91.3 Mhz ABC News Radio Warrnambool DX
91.5 Mhz Smooth FM High
91.7 Mhz ABC Northern Tasmania DX
91.9 Mhz SEN Track Low
92.1 Mhz ABC Classic Warrnambool Very low
92.3 Mhz 3ZZZ High
92.5 Mhz Radio National Somwhere Medium
92.7 Mhz ANC Classic Bendigo Medium
92.9 Mhz Low
93.1 Mhz SBSRadio High
93.3 Mhz ABC Classic DX
93.5 Mhz Vision Australia Radio High
93.7 Mhz Very low
93.9 Mhz Bay939 Medium
94.1 Mhz 3WBC Medium
94.3 Mhz TripleM Gippsland High
94.5 Mhz Very low
94.7 Mhz The Pulse Medium
94.9 Mhz Joy94.9 High
95.1 Mhz ABC News Radio High
95.3 Mhz Triple M Goulburn Valley DX
95.5 Mhz K-ROCK Medium
95.7 Mhz Golden Days Radio Medium
96.1 Mhz ABC Radio National Medium
96.3 Mhz 96three Medium
96.5 Mhz Inner FM Medium
96.7 Mhz Triple J Medium
96.9 Mhz Medium
97.1 Mhz 3MDR High
97.3 Mhz ABC Clasic Griffith NSW Low
97.7 Mhz 3SER Casey Radio High
97.9 Mhz 979fm Medium
98.1 Mhz Radio Eastern FM High
98.3 Mhz RPP FM High
98.5 Mhz Apple 98.5 FM Medium
98.7 Mhz RPP FM High
98.9 Mhz Very low
99.1 Mhz Yarra Valley FM Low
99.3 Mhz 3NRG Medium
99.5 Mhz TRFM Medium
99.7 Mhz ABC Radio National Very low
100.1 Mhz Vision Australia Radio Shepparton DX
100.3 Mhz Nova100 High
100.5 Mhz Very low
100.7 Mhz ABC Gippsland High
101.1 Mhz KIIS 101.1 High
101.5 Mhz ABC Classic High
101.7 Mhz ABC Radio National Warrnambool DX
101.9 Mhz The Fox High
102.1 Mhz ABC Mildura Swan Hill / Goschen Very low
102.3 Mhz 3BA Medium
102.7 Mhz Triple R High
103.1 Mhz Power FM Medium
103.3 Mhz DX
103.5 Mhz 3MBS High
103.7 Mhz ABC Classic Goschen / Swam Hill DX
103.9 Mhz Life FM Gippsland Medium
104.3 Mhz Gold 104.3 High
104.7 Mhz Very low
105.1 Mhz Triple M High
105.5 Mhz ABC Classic FM Ballarat Low
105.9 Mhz ABC Classic FM High
106.3 Mhz Flow FM Medium
106.5 Mhz Very low
106.7 Mhz PBS High
106.9 Mhz Very low
107.1 Mhz Triple J Low
107.5 Mhz Triple J High
107.9 Mhz ABC Ballarat Low

Note: All stations were positively identified.

I need a bigger backyard so I can put up a big FM antenna…

Some Code too!

Wow, it’s been a long time since I wrote any code. First program in Deno/Typescript to help convert the SDRSharp frequencies file to a markdown table.

// I'm too stupid to use this library
// import { parse } from "https://deno.land/x/xml/mod.ts";

// So let's pretend the xml might be terabytes in size and use a SAX library
import { SAXParser } from 'https://deno.land/x/xmlp/mod.ts';


class SignalQuality {
    protected _snr: number = 0;
    protected _isDx: boolean = false;

    set snr(value: number) {
        this._snr = value;
    }

    get snr() : number {
        return this._snr;
    }

    set isDx(value: boolean) {
        this._isDx = value;
    }
    
    get isDx() : boolean {
        return this._isDx;
    }

    protected textMap() : [string, string] {
        if(this._isDx) {
            return ['DX', 'orangered'];
        }
        else if(this._snr < 5)
        {
            return ['Very low', 'grey'];        
        }
        else if(this._snr < 10)
        {
            return ['Low', 'grey'];        
        }
        else if(this._snr < 30)
        {
            return ['Medium', '#ddd'];        
        }
        else
        {
            return ['High', 'green'];        
        }
    }

    public formattedText() : string {    
        return this.textMap()[0];
    }

    public formattedTextColor() : string {
        return this.textMap()[1];
    }
}

class MemoryEntry {
    public name: string = '';
    public frequency: number = 0;
    public notes: string = '';
    public signal: SignalQuality = new SignalQuality();

    public formattedFrequency() : string {
        return (this.frequency / (1000 * 1000)) + ' Mhz';
    }
}

async function parseFrequenciesXml(filename: string) : Promise<MemoryEntry[]>
{
    let entries = [] as MemoryEntry[];
    let currentEntry: MemoryEntry;

    let currentElement = {
        Name: false,
        Frequency: false,
        extraSNR: false,
        extraDX: false,
        extraNotes: false
    } as { [key: string]: boolean; };

    const parser = new SAXParser();

    parser.on('start_element', (el) => {
        if(el.qName == 'MemoryEntry') {
            currentEntry = new MemoryEntry();
        }
        else {
            currentElement[el.qName] = true;
        }
    }).on('end_element', (el) => {
        if(el.qName == 'MemoryEntry') {
            entries.push(currentEntry);
        }
        else {
            currentElement[el.qName] = false;
        }
    }).on('text', (s) => {
        if(currentElement['Name']) {
            currentEntry.name = s;
        }
        else if(currentElement['Frequency']) {
            currentEntry.frequency = Number(s);
        }
        if(currentElement['extraSNR']) {
            currentEntry.signal.snr = Number(s);
        }
        if(currentElement['extraDX']) {
            currentEntry.signal.isDx = (s === 'true')
        }
        if(currentElement['extraNotes']) {
            currentEntry.notes = s;
        }
    });

    // run parser, input source is Deno.Reader or Uint8Array or string
    const reader = await Deno.open(filename);
    await parser.parse(reader);
    reader.close();

    return entries;
}

let filename = "./frequencies.xml";

if(Deno.args.length == 1) {
    filename = Deno.args[0];
}

console.log(`Reading from ${filename}`);

const desc1 = { name: "read", path: filename } as const;
const status1 = await Deno.permissions.request(desc1);

let entries = await parseFrequenciesXml(filename);

console.log("| Frequency | Station Name | Signal |");
console.log("|-----------|--------------|--------|");

for(let entry of entries) {
    console.log(`| ${entry.formattedFrequency()} | ${entry.name} |  <span style="color: ${entry.signal.formattedTextColor()}">${entry.signal.formattedText()}</span> |`);
}

Finally, this is the XSD I used to export my Excel spreadsheet into a XML for both SDRSharp and the above “program”.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="ArrayOfMemoryEntry">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="MemoryEntry" minOccurs="0" maxOccurs="unbounded"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="MemoryEntry">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="IsFavourite" type="xs:string"/>
                <xs:element name="Name" type="xs:string"/>
                <xs:element name="GroupName" type="xs:string"/>
                <xs:element name="Frequency" type="xs:integer"/>
                <xs:element name="DetectorType" type="xs:string"/>
                <xs:element name="Shift" type="xs:integer"/>
                <xs:element name="FilterBandwidth" type="xs:integer"/>
                <xs:element name="extraID" type="xs:string"/>
                <xs:element name="extraSNR" type="xs:integer"/>
                <xs:element name="extraDX" type="xs:integer"/>
                <xs:element name="extraNotes" type="xs:string"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>