Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab68e4a84e | ||
|
|
f6532b2e85 | ||
|
|
8685dbd6cf | ||
|
|
b7c87def5b |
@@ -195,6 +195,7 @@ const (
|
|||||||
keyAudioQSOPlayGain = "audio.qso_play_gain" // QSO-recording playback level %
|
keyAudioQSOPlayGain = "audio.qso_play_gain" // QSO-recording playback level %
|
||||||
keyAudioPTTMethod = "audio.ptt_method" // "none" (VOX) | "rts" | "dtr"
|
keyAudioPTTMethod = "audio.ptt_method" // "none" (VOX) | "rts" | "dtr"
|
||||||
keyAudioPTTPort = "audio.ptt_port" // COM port for serial PTT
|
keyAudioPTTPort = "audio.ptt_port" // COM port for serial PTT
|
||||||
|
keyAudioPTTData = "audio.ptt_data" // keyer audio arrives on the rig DATA/USB input
|
||||||
keyAudioFormat = "audio.qso_format" // "wav" | "mp3"
|
keyAudioFormat = "audio.qso_format" // "wav" | "mp3"
|
||||||
keyAudioFromGain = "audio.from_gain" // From Radio (RX) mix level, percent
|
keyAudioFromGain = "audio.from_gain" // From Radio (RX) mix level, percent
|
||||||
keyAudioMicGain = "audio.mic_gain" // mic mix level, percent
|
keyAudioMicGain = "audio.mic_gain" // mic mix level, percent
|
||||||
@@ -8529,11 +8530,15 @@ type AudioSettings struct {
|
|||||||
PrerollSeconds int `json:"preroll_seconds"` // rolling pre-roll (default 8)
|
PrerollSeconds int `json:"preroll_seconds"` // rolling pre-roll (default 8)
|
||||||
PTTMethod string `json:"ptt_method"` // "none" (VOX) | "rts" | "dtr"
|
PTTMethod string `json:"ptt_method"` // "none" (VOX) | "rts" | "dtr"
|
||||||
PTTPort string `json:"ptt_port"` // COM port for serial PTT
|
PTTPort string `json:"ptt_port"` // COM port for serial PTT
|
||||||
Format string `json:"format"` // "wav" | "mp3"
|
// PTTData: the keyer's audio reaches the radio on its DATA/USB input, not
|
||||||
FromGain int `json:"from_gain"` // From Radio (RX) mix level %, default 100
|
// the microphone socket. CAT keying only — it changes which transmit
|
||||||
MicGain int `json:"mic_gain"` // mic mix level %, default 100
|
// command is sent (a Kenwood TS-590 takes TX1 instead of TX).
|
||||||
TXGain int `json:"tx_gain"` // voice-keyer playback level %, default 100
|
PTTData bool `json:"ptt_data"`
|
||||||
QSOPlayGain int `json:"qso_play_gain"` // QSO-recording playback level %, default 100
|
Format string `json:"format"` // "wav" | "mp3"
|
||||||
|
FromGain int `json:"from_gain"` // From Radio (RX) mix level %, default 100
|
||||||
|
MicGain int `json:"mic_gain"` // mic mix level %, default 100
|
||||||
|
TXGain int `json:"tx_gain"` // voice-keyer playback level %, default 100
|
||||||
|
QSOPlayGain int `json:"qso_play_gain"` // QSO-recording playback level %, default 100
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListAudioInputDevices / ListAudioOutputDevices enumerate WASAPI endpoints
|
// ListAudioInputDevices / ListAudioOutputDevices enumerate WASAPI endpoints
|
||||||
@@ -8590,7 +8595,7 @@ func (a *App) GetAudioSettings() (AudioSettings, error) {
|
|||||||
}
|
}
|
||||||
m, err := a.settings.GetMany(a.ctx,
|
m, err := a.settings.GetMany(a.ctx,
|
||||||
keyAudioFromRadio, keyAudioToRadio, keyAudioRecDevice, keyAudioListenDevice,
|
keyAudioFromRadio, keyAudioToRadio, keyAudioRecDevice, keyAudioListenDevice,
|
||||||
keyAudioQSORecord, keyAudioQSODir, keyAudioPreroll, keyAudioPTTMethod, keyAudioPTTPort, keyAudioFormat,
|
keyAudioQSORecord, keyAudioQSODir, keyAudioPreroll, keyAudioPTTMethod, keyAudioPTTPort, keyAudioPTTData, keyAudioFormat,
|
||||||
keyAudioFromGain, keyAudioMicGain, keyAudioTXGain, keyAudioQSOPlayGain)
|
keyAudioFromGain, keyAudioMicGain, keyAudioTXGain, keyAudioQSOPlayGain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return out, err
|
return out, err
|
||||||
@@ -8602,6 +8607,7 @@ func (a *App) GetAudioSettings() (AudioSettings, error) {
|
|||||||
out.PTTMethod = v
|
out.PTTMethod = v
|
||||||
}
|
}
|
||||||
out.PTTPort = m[keyAudioPTTPort]
|
out.PTTPort = m[keyAudioPTTPort]
|
||||||
|
out.PTTData = m[keyAudioPTTData] == "1"
|
||||||
out.FromRadio = m[keyAudioFromRadio]
|
out.FromRadio = m[keyAudioFromRadio]
|
||||||
out.ToRadio = m[keyAudioToRadio]
|
out.ToRadio = m[keyAudioToRadio]
|
||||||
out.RecordingDevice = m[keyAudioRecDevice]
|
out.RecordingDevice = m[keyAudioRecDevice]
|
||||||
@@ -8672,6 +8678,7 @@ func (a *App) SaveAudioSettings(s AudioSettings) error {
|
|||||||
keyAudioPreroll: strconv.Itoa(s.PrerollSeconds),
|
keyAudioPreroll: strconv.Itoa(s.PrerollSeconds),
|
||||||
keyAudioPTTMethod: pttMethod,
|
keyAudioPTTMethod: pttMethod,
|
||||||
keyAudioPTTPort: strings.TrimSpace(s.PTTPort),
|
keyAudioPTTPort: strings.TrimSpace(s.PTTPort),
|
||||||
|
keyAudioPTTData: boolStr(s.PTTData),
|
||||||
keyAudioFormat: format,
|
keyAudioFormat: format,
|
||||||
keyAudioFromGain: strconv.Itoa(s.FromGain),
|
keyAudioFromGain: strconv.Itoa(s.FromGain),
|
||||||
keyAudioMicGain: strconv.Itoa(s.MicGain),
|
keyAudioMicGain: strconv.Itoa(s.MicGain),
|
||||||
@@ -10599,7 +10606,7 @@ func (a *App) pttKey(cfg AudioSettings) error {
|
|||||||
if a.cat == nil {
|
if a.cat == nil {
|
||||||
return fmt.Errorf("CAT not initialized")
|
return fmt.Errorf("CAT not initialized")
|
||||||
}
|
}
|
||||||
if err := a.cat.SetPTT(true); err != nil {
|
if err := a.cat.SetPTTSource(true, cfg.PTTData); err != nil {
|
||||||
applog.Printf("ptt: CAT SetPTT failed: %v", err)
|
applog.Printf("ptt: CAT SetPTT failed: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,16 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "0.27.9",
|
||||||
|
"date": "",
|
||||||
|
"en": [
|
||||||
|
"FT decodes warn when the decoding application announces a band the radio is not on — the signature of a lost CAT link, where it repeats the last frequency it knew and every decode after that carries a stale band. Nothing downstream could tell, so NEW BAND was being judged against a band the operator had left. OpsLog says it rather than deciding: a second receiver on another band is a real setup, and it costs that one only a line to read past.",
|
||||||
|
"Voice keyer with CAT keying: an option saying the keyer’s audio arrives on the radio’s DATA / USB input rather than the microphone socket. A Kenwood TS-590 has two transmit commands — TX opens the front mic, TX1 the rear ACC2/USB — so a keyer playing through the rig’s own sound card was transmitting dead air while the radio listened to a microphone nobody was speaking into. Shown on the Kenwood backend only — no other radio family draws the distinction — and the Test PTT button exercises the same path."
|
||||||
|
],
|
||||||
|
"fr": [
|
||||||
|
"Les FT decodes signalent quand le logiciel de décodage annonce une bande sur laquelle la radio n’est pas — la signature d’une liaison CAT perdue, où il répète la dernière fréquence connue et où tous les décodages suivants portent une bande périmée. Rien en aval ne pouvait s’en apercevoir : NOUVELLE BANDE était donc jugé sur une bande quittée. OpsLog le dit sans décider à votre place : un second récepteur sur une autre bande est une configuration légitime, et il ne lui en coûte qu’une ligne à ignorer.",
|
||||||
|
"Voice keyer avec PTT CAT : une option indiquant que l’audio du keyer arrive sur l’entrée DATA / USB de la radio et non sur la prise micro. Un Kenwood TS-590 a deux commandes d’émission — TX ouvre le micro de face avant, TX1 l’ACC2/USB — si bien qu’un keyer jouant par la carte son du poste émettait dans le vide pendant que la radio écoutait un micro devant lequel personne ne parlait. Affichée sur le backend Kenwood uniquement — aucune autre famille de postes ne fait cette distinction — et le bouton Test PTT emprunte le même chemin."
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "0.27.8",
|
"version": "0.27.8",
|
||||||
"date": "",
|
"date": "",
|
||||||
|
|||||||
@@ -6328,6 +6328,9 @@ export default function App() {
|
|||||||
txState={txState}
|
txState={txState}
|
||||||
txStates={txStates}
|
txStates={txStates}
|
||||||
spotStatus={spotStatus as any}
|
spotStatus={spotStatus as any}
|
||||||
|
// Only while CAT is actually connected: an empty band means "nothing to
|
||||||
|
// compare with", never "the rig is on no band".
|
||||||
|
rigBand={catState.connected ? (catState.band || '') : ''}
|
||||||
myCall={station.callsign}
|
myCall={station.callsign}
|
||||||
// A click ANSWERS the station: it hands the decode back to WSJT-X/MSHV as
|
// A click ANSWERS the station: it hands the decode back to WSJT-X/MSHV as
|
||||||
// a Reply, which is the same thing as double-clicking the line in their
|
// a Reply, which is the same thing as double-clicking the line in their
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
// come from the same resolver the cluster uses, so a call means the same thing in
|
// come from the same resolver the cluster uses, so a call means the same thing in
|
||||||
// both panels rather than being judged twice by two rules.
|
// both panels rather than being judged twice by two rules.
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { Radio, Search, X, Signal, ArrowUpRight, Timer, Trash2, Ban, Columns2, Bot } from 'lucide-react';
|
import { AlertTriangle, Radio, Search, X, Signal, ArrowUpRight, Timer, Trash2, Ban, Columns2, Bot } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useI18n } from '@/lib/i18n';
|
import { useI18n } from '@/lib/i18n';
|
||||||
import { chaseAllows } from '@/lib/spotDisplay';
|
import { chaseAllows } from '@/lib/spotDisplay';
|
||||||
@@ -95,6 +95,9 @@ interface Props {
|
|||||||
// receiver reported last, which is a coin toss — each pane needs its own.
|
// receiver reported last, which is a coin toss — each pane needs its own.
|
||||||
txStates?: Record<string, TxMsg>;
|
txStates?: Record<string, TxMsg>;
|
||||||
spotStatus: Record<string, StatusEntry>;
|
spotStatus: Record<string, StatusEntry>;
|
||||||
|
// The band the RIG is on, when CAT is connected. Only ever compared with what
|
||||||
|
// the decoder announces — see the drift warning.
|
||||||
|
rigBand?: string;
|
||||||
onCall: (d: Decode) => void;
|
onCall: (d: Decode) => void;
|
||||||
myCall?: string;
|
myCall?: string;
|
||||||
// Drop every decode and transmit message held for this panel. The list is a
|
// Drop every decode and transmit message held for this panel. The list is a
|
||||||
@@ -539,7 +542,7 @@ function buildPeriods(filtered: Decode[], txMsgs: TxMsg[]) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DecodesPanel({ decodes, txMsgs, txState, txStates, spotStatus, onCall, myCall, onClear, onHalt, autoCallOn, onToggleAutoCall }: Props) {
|
export function DecodesPanel({ decodes, txMsgs, txState, txStates, spotStatus, rigBand, onCall, myCall, onClear, onHalt, autoCallOn, onToggleAutoCall }: Props) {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
// Column widths, dragged in the header and shared by every row. Persisted
|
// Column widths, dragged in the header and shared by every row. Persisted
|
||||||
// through writeUiPref (not raw localStorage) so the layout travels with data/
|
// through writeUiPref (not raw localStorage) so the layout travels with data/
|
||||||
@@ -597,6 +600,21 @@ export function DecodesPanel({ decodes, txMsgs, txState, txStates, spotStatus, o
|
|||||||
|
|
||||||
// The mode currently on the air, for the slot clock. The newest decode knows
|
// The mode currently on the air, for the slot clock. The newest decode knows
|
||||||
// best; between overs the transmit state still does.
|
// best; between overs the transmit state still does.
|
||||||
|
// A decoder that has lost its CAT link keeps announcing the last dial
|
||||||
|
// frequency it knew, and every decode after that carries a stale band. Nothing
|
||||||
|
// downstream can tell: the entity verdicts, the band filter and the FT map all
|
||||||
|
// believe what the decoder said, and an operator ends up reading NEW BAND for a
|
||||||
|
// band they are not on. (Seen for real: MSHV lost CAT, kept saying 80 m, and
|
||||||
|
// Korea showed as a new band because on 80 m it would have been.)
|
||||||
|
//
|
||||||
|
// Said, not decided. Using the rig's band instead would be wrong for anyone
|
||||||
|
// decoding a second receiver on another band, and a warning costs that setup
|
||||||
|
// nothing but a line it can read past.
|
||||||
|
const decoderBand = decodes.length ? (decodes[decodes.length - 1].band ?? '') : '';
|
||||||
|
const bandDrift = !!rigBand && !!decoderBand
|
||||||
|
&& rigBand.toLowerCase() !== decoderBand.toLowerCase();
|
||||||
|
const driftInstance = decodes.length ? (decodes[decodes.length - 1].instance ?? '') : '';
|
||||||
|
|
||||||
const liveMode = decodes.length ? decodes[decodes.length - 1].mode : txState?.mode;
|
const liveMode = decodes.length ? decodes[decodes.length - 1].mode : txState?.mode;
|
||||||
const liveTr = trSeconds(liveMode, decodes.length ? decodes[decodes.length - 1].tr_period : undefined);
|
const liveTr = trSeconds(liveMode, decodes.length ? decodes[decodes.length - 1].tr_period : undefined);
|
||||||
|
|
||||||
@@ -733,6 +751,18 @@ export function DecodesPanel({ decodes, txMsgs, txState, txStates, spotStatus, o
|
|||||||
what the transmit state reports, so it is right the moment anything
|
what the transmit state reports, so it is right the moment anything
|
||||||
is heard and keeps running when the band goes quiet. */}
|
is heard and keeps running when the band goes quiet. */}
|
||||||
<PeriodClock trSec={liveTr} mode={liveMode} />
|
<PeriodClock trSec={liveTr} mode={liveMode} />
|
||||||
|
{bandDrift && (
|
||||||
|
<span
|
||||||
|
title={t('dec.bandDriftTip')}
|
||||||
|
className="inline-flex items-center gap-1 rounded-full border border-warning px-2 py-0.5 text-[11px] font-semibold text-warning">
|
||||||
|
<AlertTriangle className="size-3.5" />
|
||||||
|
{t('dec.bandDrift', {
|
||||||
|
app: driftInstance || t('dec.bandDriftApp'),
|
||||||
|
dec: decoderBand.toUpperCase(),
|
||||||
|
rig: (rigBand ?? '').toUpperCase(),
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<span className="w-px h-5 bg-border/60 mx-1" />
|
<span className="w-px h-5 bg-border/60 mx-1" />
|
||||||
|
|
||||||
<button type="button" className={chip(cqOnly, 'success')} onClick={() => setCqOnly(!cqOnly)}>
|
<button type="button" className={chip(cqOnly, 'success')} onClick={() => setCqOnly(!cqOnly)}>
|
||||||
|
|||||||
@@ -1702,13 +1702,13 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
|
|||||||
type AudioSettings = {
|
type AudioSettings = {
|
||||||
from_radio: string; to_radio: string; recording_device: string; listening_device: string;
|
from_radio: string; to_radio: string; recording_device: string; listening_device: string;
|
||||||
qso_record: boolean; qso_dir: string; preroll_seconds: number;
|
qso_record: boolean; qso_dir: string; preroll_seconds: number;
|
||||||
ptt_method: 'none' | 'cat' | 'rts' | 'dtr'; ptt_port: string; format: 'wav' | 'mp3';
|
ptt_method: 'none' | 'cat' | 'rts' | 'dtr'; ptt_port: string; ptt_data?: boolean; format: 'wav' | 'mp3';
|
||||||
from_gain: number; mic_gain: number; tx_gain: number; qso_play_gain: number;
|
from_gain: number; mic_gain: number; tx_gain: number; qso_play_gain: number;
|
||||||
};
|
};
|
||||||
type AudioDev = { id: string; name: string; default: boolean };
|
type AudioDev = { id: string; name: string; default: boolean };
|
||||||
const [audioCfg, setAudioCfg] = useState<AudioSettings>({
|
const [audioCfg, setAudioCfg] = useState<AudioSettings>({
|
||||||
from_radio: '', to_radio: '', recording_device: '', listening_device: '',
|
from_radio: '', to_radio: '', recording_device: '', listening_device: '',
|
||||||
qso_record: false, qso_dir: '', preroll_seconds: 8, ptt_method: 'none', ptt_port: '', format: 'wav',
|
qso_record: false, qso_dir: '', preroll_seconds: 8, ptt_method: 'none', ptt_port: '', ptt_data: false, format: 'wav',
|
||||||
from_gain: 100, mic_gain: 100, tx_gain: 100, qso_play_gain: 100,
|
from_gain: 100, mic_gain: 100, tx_gain: 100, qso_play_gain: 100,
|
||||||
});
|
});
|
||||||
const [audioInputs, setAudioInputs] = useState<AudioDev[]>([]);
|
const [audioInputs, setAudioInputs] = useState<AudioDev[]>([]);
|
||||||
@@ -7251,6 +7251,21 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{/* Kenwood only, because only a Kenwood acts on it: TX1 is that
|
||||||
|
family's second transmit command. Every other backend keys the
|
||||||
|
one way it knows, so showing the box there would be a switch
|
||||||
|
that changes nothing — the same dead furniture as ANT2 on a
|
||||||
|
radio with one socket. */}
|
||||||
|
{audioCfg.ptt_method === 'cat' && catCfg.backend === 'kenwood' && (
|
||||||
|
<>
|
||||||
|
<span />
|
||||||
|
<label className="flex items-start gap-2 text-sm cursor-pointer" title={t('aud.pttDataHint')}>
|
||||||
|
<Checkbox className="mt-0.5" checked={!!audioCfg.ptt_data}
|
||||||
|
onCheckedChange={(c) => setAudioField({ ptt_data: !!c })} />
|
||||||
|
<span>{t('aud.pttData')}</span>
|
||||||
|
</label>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{(audioCfg.ptt_method === 'rts' || audioCfg.ptt_method === 'dtr') && (
|
{(audioCfg.ptt_method === 'rts' || audioCfg.ptt_method === 'dtr') && (
|
||||||
<>
|
<>
|
||||||
<Label className="text-sm">{t('aud.pttPort')}</Label>
|
<Label className="text-sm">{t('aud.pttPort')}</Label>
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ const en: Dict = {
|
|||||||
'mx.tipThisCall': 'already worked with this callsign',
|
'mx.tipThisCall': 'already worked with this callsign',
|
||||||
'mx.tipThisCallConf': 'already confirmed with this callsign',
|
'mx.tipThisCallConf': 'already confirmed with this callsign',
|
||||||
// FTx decodes panel (Tools -> FT decodes)
|
// FTx decodes panel (Tools -> FT decodes)
|
||||||
'ftmap.tab': 'FT Map', 'ftmap.noGrid': 'Set your station grid in Preferences to place the map.', 'dec.tab': 'FT decodes', 'dec.title': 'Decodes', 'dec.cqOnly': 'CQ only',
|
'ftmap.tab': 'FT Map', 'ftmap.noGrid': 'Set your station grid in Preferences to place the map.', 'dec.tab': 'FT decodes', 'dec.title': 'Decodes', 'dec.bandDrift': '{app} says {dec}, the rig is on {rig}', 'dec.bandDriftApp': 'the decoder', 'dec.bandDriftTip': 'The decoding application is announcing a band the radio is not on — it has most likely lost its CAT link and is repeating the last frequency it knew. Every decode below carries that band, so the NEW / NEW BAND verdicts are judged against it.', 'dec.cqOnly': 'CQ only',
|
||||||
'dec.allBands': 'All bands', 'dec.allModes': 'All modes', 'toast.qsoLogged': 'QSO logged', 'dec.contsHint': 'Continents: click to keep one or several', 'dec.allConts': 'All continents',
|
'dec.allBands': 'All bands', 'dec.allModes': 'All modes', 'toast.qsoLogged': 'QSO logged', 'dec.contsHint': 'Continents: click to keep one or several', 'dec.allConts': 'All continents',
|
||||||
'dec.minSnrTitle': 'Hide anything weaker than this SNR', 'dec.searchPh': 'Call, grid or message',
|
'dec.minSnrTitle': 'Hide anything weaker than this SNR', 'dec.searchPh': 'Call, grid or message',
|
||||||
'dec.clearFilters': 'Clear', 'dec.live': 'live', 'dec.tx': 'TX',
|
'dec.clearFilters': 'Clear', 'dec.live': 'live', 'dec.tx': 'TX',
|
||||||
@@ -535,7 +535,7 @@ const en: Dict = {
|
|||||||
'aud.preroll': 'Pre-roll (seconds)', 'aud.format': 'File format', 'aud.wav': 'WAV (lossless, larger)', 'aud.mp3': 'MP3 (compressed, small)',
|
'aud.preroll': 'Pre-roll (seconds)', 'aud.format': 'File format', 'aud.wav': 'WAV (lossless, larger)', 'aud.mp3': 'MP3 (compressed, small)',
|
||||||
'aud.fromLevel': 'From Radio level', 'aud.txLevel': 'Voice keyer level', 'aud.txLevelHint': 'Level of the recorded messages sent to the radio. Raise it if your voice keyer is much quieter than your microphone; Play previews at this same level. If the radio transmits almost nothing, its modulation source is still the front microphone: on an FTDX10 set MENU → SSB MOD SOURCE to REAR (the USB input).', 'aud.micLevel': 'Mic level', 'aud.qsoPlayLevel': 'QSO playback level', 'aud.levelHint': 'If your voice is louder than the station, lower Mic level.',
|
'aud.fromLevel': 'From Radio level', 'aud.txLevel': 'Voice keyer level', 'aud.txLevelHint': 'Level of the recorded messages sent to the radio. Raise it if your voice keyer is much quieter than your microphone; Play previews at this same level. If the radio transmits almost nothing, its modulation source is still the front microphone: on an FTDX10 set MENU → SSB MOD SOURCE to REAR (the USB input).', 'aud.micLevel': 'Mic level', 'aud.qsoPlayLevel': 'QSO playback level', 'aud.levelHint': 'If your voice is louder than the station, lower Mic level.',
|
||||||
'aud.autoSend': 'Auto-send the recording to the station by e-mail when I log a QSO',
|
'aud.autoSend': 'Auto-send the recording to the station by e-mail when I log a QSO',
|
||||||
'aud.dvkTitle': 'Voice keyer messages (F1–F12)', 'aud.deleteMsg': 'Delete this message', 'aud.pttMethod': 'PTT method', 'aud.pttNone': 'None (VOX)', 'aud.pttCat': 'CAT (the radio link)', 'aud.pttRts': 'Serial RTS', 'aud.pttDtr': 'Serial DTR',
|
'aud.dvkTitle': 'Voice keyer messages (F1–F12)', 'aud.deleteMsg': 'Delete this message', 'aud.pttData': 'Kenwood: the keyer’s audio arrives on the DATA / USB input', 'aud.pttDataHint': 'Sends TX1 (ACC2/USB) instead of TX (front microphone) — the TS-590 family’s second transmit command. Without it the radio transmits while listening to a microphone nobody is speaking into. Kenwood only: no other backend has the distinction.', 'aud.pttMethod': 'PTT method', 'aud.pttNone': 'None (VOX)', 'aud.pttCat': 'CAT (the radio link)', 'aud.pttRts': 'Serial RTS', 'aud.pttDtr': 'Serial DTR',
|
||||||
'aud.testPtt': 'Test PTT', 'aud.pttPort': 'PTT COM port', 'aud.pickPort': 'Pick a COM port', 'aud.selectPort': '— select —', 'aud.refresh': 'Refresh',
|
'aud.testPtt': 'Test PTT', 'aud.pttPort': 'PTT COM port', 'aud.pickPort': 'Pick a COM port', 'aud.selectPort': '— select —', 'aud.refresh': 'Refresh',
|
||||||
'aud.msgPlaceholder': 'Message {n} label (CQ, report, 73…)', 'aud.holdRec': '● Hold to rec', 'aud.recordingNow': '● Recording…', 'aud.play': '▶ Play', 'aud.stop': '■ Stop',
|
'aud.msgPlaceholder': 'Message {n} label (CQ, report, 73…)', 'aud.holdRec': '● Hold to rec', 'aud.recordingNow': '● Recording…', 'aud.play': '▶ Play', 'aud.stop': '■ Stop',
|
||||||
'aud.errPttTest': 'PTT test: ', 'aud.errRecord': 'Record: ', 'aud.errSave': 'Save: ', 'aud.errPlay': 'Play: ',
|
'aud.errPttTest': 'PTT test: ', 'aud.errRecord': 'Record: ', 'aud.errSave': 'Save: ', 'aud.errPlay': 'Play: ',
|
||||||
@@ -682,7 +682,7 @@ const fr: Dict = {
|
|||||||
'mx.tipThisCall': 'déjà contacté avec cet indicatif',
|
'mx.tipThisCall': 'déjà contacté avec cet indicatif',
|
||||||
'mx.tipThisCallConf': 'déjà confirmé avec cet indicatif',
|
'mx.tipThisCallConf': 'déjà confirmé avec cet indicatif',
|
||||||
// Panneau des decodes FTx (Outils -> Decodes FT)
|
// Panneau des decodes FTx (Outils -> Decodes FT)
|
||||||
'ftmap.tab': 'FT Map', 'ftmap.noGrid': 'Renseigne ton locator dans les Préférences pour placer la carte.', 'dec.tab': 'Decodes FT', 'dec.title': 'Decodes', 'dec.cqOnly': 'CQ seulement',
|
'ftmap.tab': 'FT Map', 'ftmap.noGrid': 'Renseigne ton locator dans les Préférences pour placer la carte.', 'dec.tab': 'Decodes FT', 'dec.title': 'Decodes', 'dec.bandDrift': '{app} annonce {dec}, le poste est sur {rig}', 'dec.bandDriftApp': 'le décodeur', 'dec.bandDriftTip': 'Le logiciel de décodage annonce une bande sur laquelle la radio n’est pas — il a très probablement perdu sa liaison CAT et répète la dernière fréquence connue. Tous les décodages ci-dessous portent cette bande, et les verdicts NOUVEAU / NOUVELLE BANDE sont jugés dessus.', 'dec.cqOnly': 'CQ seulement',
|
||||||
'dec.allBands': 'Toutes bandes', 'dec.allModes': 'Tous modes', 'toast.qsoLogged': 'QSO enregistré', 'dec.contsHint': 'Continents : clique pour en garder un ou plusieurs', 'dec.allConts': 'Tous continents',
|
'dec.allBands': 'Toutes bandes', 'dec.allModes': 'Tous modes', 'toast.qsoLogged': 'QSO enregistré', 'dec.contsHint': 'Continents : clique pour en garder un ou plusieurs', 'dec.allConts': 'Tous continents',
|
||||||
'dec.minSnrTitle': 'Masquer tout ce qui est plus faible que ce rapport', 'dec.searchPh': 'Indicatif, locator ou message',
|
'dec.minSnrTitle': 'Masquer tout ce qui est plus faible que ce rapport', 'dec.searchPh': 'Indicatif, locator ou message',
|
||||||
'dec.clearFilters': 'Effacer', 'dec.live': 'en direct', 'dec.tx': 'TX',
|
'dec.clearFilters': 'Effacer', 'dec.live': 'en direct', 'dec.tx': 'TX',
|
||||||
@@ -1037,7 +1037,7 @@ const fr: Dict = {
|
|||||||
'aud.preroll': 'Pré-enregistrement (secondes)', 'aud.format': 'Format de fichier', 'aud.wav': 'WAV (sans perte, plus volumineux)', 'aud.mp3': 'MP3 (compressé, léger)',
|
'aud.preroll': 'Pré-enregistrement (secondes)', 'aud.format': 'Format de fichier', 'aud.wav': 'WAV (sans perte, plus volumineux)', 'aud.mp3': 'MP3 (compressé, léger)',
|
||||||
'aud.fromLevel': 'Niveau depuis la radio', 'aud.txLevel': 'Niveau du voice keyer', 'aud.txLevelHint': "Niveau des messages enregistrés envoyés à la radio. Augmentez-le si votre voice keyer est bien plus faible que votre micro ; Lire fait entendre ce même niveau. Si la radio n'émet presque rien, c'est que sa source de modulation est restée le micro de façade : sur un FTDX10, réglez MENU → SSB MOD SOURCE sur REAR (l'entrée USB).", 'aud.micLevel': 'Niveau micro', 'aud.qsoPlayLevel': 'Niveau de relecture QSO', 'aud.levelHint': 'Si votre voix est plus forte que la station, baissez le niveau micro.',
|
'aud.fromLevel': 'Niveau depuis la radio', 'aud.txLevel': 'Niveau du voice keyer', 'aud.txLevelHint': "Niveau des messages enregistrés envoyés à la radio. Augmentez-le si votre voice keyer est bien plus faible que votre micro ; Lire fait entendre ce même niveau. Si la radio n'émet presque rien, c'est que sa source de modulation est restée le micro de façade : sur un FTDX10, réglez MENU → SSB MOD SOURCE sur REAR (l'entrée USB).", 'aud.micLevel': 'Niveau micro', 'aud.qsoPlayLevel': 'Niveau de relecture QSO', 'aud.levelHint': 'Si votre voix est plus forte que la station, baissez le niveau micro.',
|
||||||
'aud.autoSend': "Envoyer automatiquement l'enregistrement à la station par e-mail lorsque j'enregistre un QSO",
|
'aud.autoSend': "Envoyer automatiquement l'enregistrement à la station par e-mail lorsque j'enregistre un QSO",
|
||||||
'aud.dvkTitle': 'Messages du manipulateur vocal (F1–F12)', 'aud.deleteMsg': 'Supprimer ce message', 'aud.pttMethod': 'Méthode PTT', 'aud.pttNone': 'Aucune (VOX)', 'aud.pttCat': 'CAT (la liaison radio)', 'aud.pttRts': 'RTS série', 'aud.pttDtr': 'DTR série',
|
'aud.dvkTitle': 'Messages du manipulateur vocal (F1–F12)', 'aud.deleteMsg': 'Supprimer ce message', 'aud.pttData': 'Kenwood : l’audio du keyer arrive sur l’entrée DATA / USB', 'aud.pttDataHint': 'Envoie TX1 (ACC2/USB) au lieu de TX (micro de face avant) — la seconde commande d’émission de la famille TS-590. Sans cela la radio émet en écoutant un micro devant lequel personne ne parle. Kenwood uniquement : aucun autre backend n’a cette distinction.', 'aud.pttMethod': 'Méthode PTT', 'aud.pttNone': 'Aucune (VOX)', 'aud.pttCat': 'CAT (la liaison radio)', 'aud.pttRts': 'RTS série', 'aud.pttDtr': 'DTR série',
|
||||||
'aud.testPtt': 'Tester le PTT', 'aud.pttPort': 'Port COM du PTT', 'aud.pickPort': 'Choisir un port COM', 'aud.selectPort': '— choisir —', 'aud.refresh': 'Actualiser',
|
'aud.testPtt': 'Tester le PTT', 'aud.pttPort': 'Port COM du PTT', 'aud.pickPort': 'Choisir un port COM', 'aud.selectPort': '— choisir —', 'aud.refresh': 'Actualiser',
|
||||||
'aud.msgPlaceholder': 'Libellé du message {n} (CQ, report, 73…)', 'aud.holdRec': '● Maintenir pour enreg.', 'aud.recordingNow': '● Enregistrement…', 'aud.play': '▶ Lire', 'aud.stop': '■ Arrêter',
|
'aud.msgPlaceholder': 'Libellé du message {n} (CQ, report, 73…)', 'aud.holdRec': '● Maintenir pour enreg.', 'aud.recordingNow': '● Enregistrement…', 'aud.play': '▶ Lire', 'aud.stop': '■ Arrêter',
|
||||||
'aud.errPttTest': 'Test PTT : ', 'aud.errRecord': 'Enregistrement : ', 'aud.errSave': 'Sauvegarde : ', 'aud.errPlay': 'Lecture : ',
|
'aud.errPttTest': 'Test PTT : ', 'aud.errRecord': 'Enregistrement : ', 'aud.errSave': 'Sauvegarde : ', 'aud.errPlay': 'Lecture : ',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Single source of truth for the app version shown in the UI (header + About).
|
// Single source of truth for the app version shown in the UI (header + About).
|
||||||
// Bump this on a release (the release script updates it alongside telemetry.go).
|
// Bump this on a release (the release script updates it alongside telemetry.go).
|
||||||
export const APP_VERSION = '0.27.8';
|
export const APP_VERSION = '0.27.9';
|
||||||
|
|
||||||
// Author / credits, shown in Help -> About.
|
// Author / credits, shown in Help -> About.
|
||||||
export const APP_AUTHOR = 'F4BPO';
|
export const APP_AUTHOR = 'F4BPO';
|
||||||
|
|||||||
@@ -1924,6 +1924,7 @@ export namespace main {
|
|||||||
preroll_seconds: number;
|
preroll_seconds: number;
|
||||||
ptt_method: string;
|
ptt_method: string;
|
||||||
ptt_port: string;
|
ptt_port: string;
|
||||||
|
ptt_data: boolean;
|
||||||
format: string;
|
format: string;
|
||||||
from_gain: number;
|
from_gain: number;
|
||||||
mic_gain: number;
|
mic_gain: number;
|
||||||
@@ -1945,6 +1946,7 @@ export namespace main {
|
|||||||
this.preroll_seconds = source["preroll_seconds"];
|
this.preroll_seconds = source["preroll_seconds"];
|
||||||
this.ptt_method = source["ptt_method"];
|
this.ptt_method = source["ptt_method"];
|
||||||
this.ptt_port = source["ptt_port"];
|
this.ptt_port = source["ptt_port"];
|
||||||
|
this.ptt_data = source["ptt_data"];
|
||||||
this.format = source["format"];
|
this.format = source["format"];
|
||||||
this.from_gain = source["from_gain"];
|
this.from_gain = source["from_gain"];
|
||||||
this.mic_gain = source["mic_gain"];
|
this.mic_gain = source["mic_gain"];
|
||||||
|
|||||||
@@ -256,6 +256,33 @@ func (m *Manager) SetPTT(on bool) error {
|
|||||||
return m.exec(func(b Backend) error { return b.SetPTT(on) })
|
return m.exec(func(b Backend) error { return b.SetPTT(on) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dataPTTSetter is implemented by a backend that can key the DATA input rather
|
||||||
|
// than the microphone. A Kenwood TS-590 has two transmit commands and takes its
|
||||||
|
// audio from a different socket for each: TX (or TX0) opens the front mic, TX1
|
||||||
|
// the rear ACC2/USB. Send the wrong one and the radio transmits in silence,
|
||||||
|
// because the audio arriving on USB is simply not the input it is listening to.
|
||||||
|
type dataPTTSetter interface {
|
||||||
|
SetPTTData(on bool) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPTTSource keys the transmitter, saying WHERE the audio is coming from.
|
||||||
|
//
|
||||||
|
// data=true means "the audio reaches the radio on its data/USB input" — what a
|
||||||
|
// voice keyer playing through the rig's own sound card needs. A backend that
|
||||||
|
// draws no distinction (every rig where one PTT is all there is) falls back to
|
||||||
|
// the ordinary key, so nothing changes for it.
|
||||||
|
func (m *Manager) SetPTTSource(on, data bool) error {
|
||||||
|
if !data {
|
||||||
|
return m.SetPTT(on)
|
||||||
|
}
|
||||||
|
return m.exec(func(b Backend) error {
|
||||||
|
if d, ok := b.(dataPTTSetter); ok {
|
||||||
|
return d.SetPTTData(on)
|
||||||
|
}
|
||||||
|
return b.SetPTT(on)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// splitSetter is implemented by the backends that can arm split AND place the
|
// splitSetter is implemented by the backends that can arm split AND place the
|
||||||
// transmit frequency. Both together: arming without setting the dial transmits
|
// transmit frequency. Both together: arming without setting the dial transmits
|
||||||
// on whatever the transmit VFO happened to hold, which is worse than refusing.
|
// on whatever the transmit VFO happened to hold, which is worse than refusing.
|
||||||
|
|||||||
@@ -632,6 +632,28 @@ func (k *Kenwood) SetPTT(on bool) error {
|
|||||||
return k.write("RX;")
|
return k.write("RX;")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetPTTData keys the transmitter on the DATA input: TX1 on a TS-590, which is
|
||||||
|
// ACC2/USB rather than the front microphone. The radio's own manual is explicit
|
||||||
|
// that the parameter chooses the input — "0: SEND (normal transmission using
|
||||||
|
// the MIC input), 1: DATA SEND (ACC2/USB input)" — so a voice keyer playing
|
||||||
|
// into the rig's USB codec has to say TX1 or it transmits dead air while the
|
||||||
|
// radio listens to a microphone nobody is speaking into.
|
||||||
|
//
|
||||||
|
// Unkeying is the same RX either way; there is no data-flavoured stop.
|
||||||
|
func (k *Kenwood) SetPTTData(on bool) error {
|
||||||
|
k.mu.Lock()
|
||||||
|
defer k.mu.Unlock()
|
||||||
|
if k.port == nil {
|
||||||
|
return fmt.Errorf("kenwood: not connected")
|
||||||
|
}
|
||||||
|
k.tx = on
|
||||||
|
if on {
|
||||||
|
k.txAt = time.Now()
|
||||||
|
return k.write("TX1;")
|
||||||
|
}
|
||||||
|
return k.write("RX;")
|
||||||
|
}
|
||||||
|
|
||||||
func (k *Kenwood) write(cmd string) error {
|
func (k *Kenwood) write(cmd string) error {
|
||||||
if k.port == nil {
|
if k.port == nil {
|
||||||
return fmt.Errorf("kenwood: not connected")
|
return fmt.Errorf("kenwood: not connected")
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// appVersion is stamped on every heartbeat (and could feed the About box).
|
// appVersion is stamped on every heartbeat (and could feed the About box).
|
||||||
appVersion = "0.27.8"
|
appVersion = "0.27.9"
|
||||||
|
|
||||||
// posthogHost is the PostHog ingestion endpoint. EU cloud by default; change
|
// posthogHost is the PostHog ingestion endpoint. EU cloud by default; change
|
||||||
// to https://us.i.posthog.com for a US project.
|
// to https://us.i.posthog.com for a US project.
|
||||||
|
|||||||
Reference in New Issue
Block a user