Paint the band the way N1MM paints it

Behind the bandmap scale, the CW part of the band is one shade and the phone
part another, with a third for a digital block. That is what N1MM's graphical
bandmap does: ResizeBandPanels puts a CW, a Digi and an SSB panel behind the
scale, sized from its CWBands, DigiBands and SSBBands tables. Anything past a
band edge is shaded red, which is the only place the edges themselves show, and
it only appears now that the slice can reach past an edge.

The defaults are N1MM's: CW ends at 1840, 3600, 7125, 14150 and so on, and the
digital pair is empty until somebody fills it in, exactly as N1MM ships. They
are one worldwide set rather than one per ITU region, which is also N1MM's
answer — the reason recorded for leaving this out, that the segments differ by
region, was wrong about what N1MM holds. 60M and everything above 2M get no
entry, because N1MM's own numbers there contradict themselves: its 1.25M CW top
is below its phone start, and its 70CM CW top is above the band.

Config ▸ Sub bands edits them, one row per band in kilohertz, with a Defaults
button. Only the rows that differ are written to the settings file, so a plan
nobody has touched leaves nothing behind. BandPlan is an object rather than a
static table, so the stored changes layer over the defaults and the bandmap
picks up a save without a restart.

The numbers are a drawing aid, not a licence. Nothing stops a contact being
logged either side of a boundary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 23:01:56 +00:00
parent e6d0b58305
commit d084789ed2
12 changed files with 431 additions and 7 deletions

View File

@@ -0,0 +1,61 @@
using Nonemm.Core;
namespace Nonemm.Core.Tests;
public class BandPlanTests
{
[Fact]
public void TwentyMetresSplitsAtTheCwEdge()
{
BandSegments segments = Assert.IsType<BandSegments>(BandPlan.Default.For(Bands.Band20M));
Assert.Equal(Bands.Band20M.Low, segments.CwLow);
Assert.Equal(Frequency.FromKilohertz(14_150), segments.CwHigh);
Assert.Equal(Frequency.FromKilohertz(14_150), segments.PhoneLow);
Assert.Equal(Bands.Band20M.High, segments.PhoneHigh);
Assert.True(segments.HasPhone);
Assert.False(segments.HasDigital);
}
[Fact]
public void ThirtyMetresIsAllCw()
{
BandSegments segments = Assert.IsType<BandSegments>(BandPlan.Default.For(Bands.Band30M));
Assert.Equal(Bands.Band30M.High, segments.CwHigh);
Assert.False(segments.HasPhone);
}
/// Nothing is painted for a band with no boundary.
[Theory]
[InlineData("60M")]
[InlineData("70CM")]
[InlineData("630M")]
public void ABandWithNoBoundaryHasNoSegments(string name) =>
Assert.Null(BandPlan.Default.For(Bands.Named(name)!));
[Fact]
public void ChangingOneBandLeavesTheOthersAlone()
{
BandPlan changed = BandPlan.Default.With(
new BandSegments(Bands.Band20M, Frequency.FromKilohertz(14_100)));
Assert.Equal(Frequency.FromKilohertz(14_100), changed.For(Bands.Band20M)!.CwHigh);
Assert.Equal(
BandPlan.Default.For(Bands.Band40M)!.CwHigh,
changed.For(Bands.Band40M)!.CwHigh);
Assert.Equal(BandPlan.Default.All.Count, changed.All.Count);
}
[Fact]
public void ADigitalBlockIsThereOnceItHasWidth()
{
BandSegments segments = new(
Bands.Band20M,
Frequency.FromKilohertz(14_150),
Frequency.FromKilohertz(14_070),
Frequency.FromKilohertz(14_099));
Assert.True(segments.HasDigital);
}
}