@springfield/ham-radio-utils 4.6.0 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"memory-map-channels.d.ts","sourceRoot":"","sources":["../../src/memory/memory-map-channels.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAEnB,KAAK,YAAY,EACjB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAInB,MAAM,4BAA4B,CAAC;AAuCpC;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,aAAa,GACtB,sBAAsB,EAAE,CA0E1B;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,aAAa,GAAG,aAAa,CAezG;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,YAAY,GAAG,aAAa,CA6ExG;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,iBAAiB,GAC9B,YAAY,CAOd;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,cAAc,EACzB,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,iBAAiB,GAC9B,UAAU,CAGZ"}
1
+ {"version":3,"file":"memory-map-channels.d.ts","sourceRoot":"","sources":["../../src/memory/memory-map-channels.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAEnB,KAAK,YAAY,EACjB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAInB,MAAM,4BAA4B,CAAC;AA2HpC;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,aAAa,GACtB,sBAAsB,EAAE,CAsG1B;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,aAAa,GAAG,aAAa,CAmBzG;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,YAAY,GAAG,aAAa,CAwJxG;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,iBAAiB,GAC9B,YAAY,CAOd;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,cAAc,EACzB,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,iBAAiB,GAC9B,UAAU,CAGZ"}
@@ -21,6 +21,67 @@ function radioToneToMapTone(tone) {
21
21
  }
22
22
  return { mode: 'ctcss', value: Number(tone.tone) };
23
23
  }
24
+ function noneTone() {
25
+ return { tone: 0, type: RadioToneType.CTCSS };
26
+ }
27
+ function isNoneTone(tone) {
28
+ return !tone || !tone.tone;
29
+ }
30
+ function structHasField(struct, fieldId) {
31
+ return Boolean(struct?.fields.some((field) => field.id === fieldId));
32
+ }
33
+ function transmitFrequencyFromRecord(record, bindings) {
34
+ const receiveFrequency = Number(record[bindings.receiveFrequency] ?? 0);
35
+ const offsetOrTransmit = Number(record[bindings.transmitFrequency] ?? 0);
36
+ if (record.split === true) {
37
+ return offsetOrTransmit;
38
+ }
39
+ if (record.duplex === '+') {
40
+ return receiveFrequency + offsetOrTransmit;
41
+ }
42
+ if (record.duplex === '-') {
43
+ return receiveFrequency - offsetOrTransmit;
44
+ }
45
+ // Kenwood stores simplex as duplex "" and offset 0. Empty duplex is not an absolute TX frequency.
46
+ if (record.duplex === '') {
47
+ return receiveFrequency;
48
+ }
49
+ if (record.duplex === undefined) {
50
+ return bindings.transmitFrequency === bindings.receiveFrequency ? receiveFrequency : offsetOrTransmit;
51
+ }
52
+ return offsetOrTransmit;
53
+ }
54
+ function applyKenwoodToneMode(record, bindings) {
55
+ const hasToneModeBits = 'tone_mode' in record || 'ctcss_mode' in record || 'dtcs_mode' in record;
56
+ if (!hasToneModeBits) {
57
+ return {
58
+ receiveTone: toneToRadioTone(record[bindings.receiveTone]),
59
+ transmitTone: toneToRadioTone(record[bindings.transmitTone]),
60
+ };
61
+ }
62
+ if (record.tone_mode === true) {
63
+ return { receiveTone: noneTone(), transmitTone: toneToRadioTone(record[bindings.transmitTone]) };
64
+ }
65
+ if (record.ctcss_mode === true) {
66
+ const tone = toneToRadioTone(record[bindings.receiveTone]);
67
+ return { receiveTone: tone, transmitTone: tone };
68
+ }
69
+ if (record.dtcs_mode === true) {
70
+ const tone = toneToRadioTone(record.dtcs_code);
71
+ return { receiveTone: tone, transmitTone: tone };
72
+ }
73
+ return { receiveTone: noneTone(), transmitTone: noneTone() };
74
+ }
75
+ function kenwoodUsedFlag(receiveFrequency, transmitFrequency) {
76
+ const frequency = transmitFrequency || receiveFrequency;
77
+ if (frequency < 150_000_000) {
78
+ return 0;
79
+ }
80
+ if (frequency < 400_000_000) {
81
+ return 1;
82
+ }
83
+ return 2;
84
+ }
24
85
  function asRecord(value) {
25
86
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
26
87
  return undefined;
@@ -37,26 +98,35 @@ export function bindingsToChannels(memoryMap, settings) {
37
98
  }
38
99
  const records = settings[bindings.records];
39
100
  const names = bindings.names ? settings[bindings.names] : undefined;
101
+ const extras = bindings.extras ? settings[bindings.extras] : undefined;
40
102
  const recordItems = Array.isArray(records) ? records : [];
41
103
  const nameItems = Array.isArray(names) ? names : [];
104
+ const extraItems = Array.isArray(extras) ? extras : [];
42
105
  const nameField = bindings.nameField ?? 'name';
43
106
  const boundFieldIds = new Set([
44
107
  bindings.receiveFrequency,
45
108
  bindings.transmitFrequency,
46
109
  bindings.receiveTone,
47
110
  bindings.transmitTone,
111
+ nameField,
48
112
  ]);
49
113
  const channels = [];
50
114
  for (let index = 0; index < recordItems.length; index += 1) {
51
115
  const record = asRecord(recordItems[index]);
116
+ const extraRecord = asRecord(extraItems[index]);
117
+ if (bindings.extras && extraItems.length > 0 && extraRecord === undefined) {
118
+ continue;
119
+ }
52
120
  if (!record) {
53
121
  continue;
54
122
  }
55
123
  const nameRecord = asRecord(nameItems[index]);
56
- const nameValue = nameRecord?.[nameField];
57
- const name = typeof nameValue === 'string' ? nameValue : '';
124
+ const nameFromTable = typeof nameRecord?.[nameField] === 'string' ? nameRecord[nameField] : '';
125
+ const nameFromRecord = typeof record[nameField] === 'string' ? record[nameField] : '';
126
+ const name = nameFromTable || nameFromRecord;
58
127
  const receiveFrequency = Number(record[bindings.receiveFrequency] ?? 0);
59
- const transmitFrequency = Number(record[bindings.transmitFrequency] ?? 0);
128
+ const transmitFrequency = transmitFrequencyFromRecord(record, bindings);
129
+ const tones = applyKenwoodToneMode(record, bindings);
60
130
  const channelSettings = {};
61
131
  for (const [fieldId, fieldValue] of Object.entries(record)) {
62
132
  if (boundFieldIds.has(fieldId) || fieldId.startsWith('_')) {
@@ -64,6 +134,17 @@ export function bindingsToChannels(memoryMap, settings) {
64
134
  }
65
135
  channelSettings[fieldId] = fieldValue;
66
136
  }
137
+ if (extraRecord) {
138
+ for (const [fieldId, fieldValue] of Object.entries(extraRecord)) {
139
+ if (fieldId.startsWith('_') || fieldId === 'used') {
140
+ continue;
141
+ }
142
+ channelSettings[fieldId] = fieldValue;
143
+ }
144
+ if (typeof extraRecord.lockout === 'boolean') {
145
+ channelSettings.skip = extraRecord.lockout ? 'S' : '';
146
+ }
147
+ }
67
148
  // Map Chirp lowpower index onto legacy transmitPower watts for UV-5R UI compatibility.
68
149
  if (typeof channelSettings.lowpower === 'number') {
69
150
  channelSettings.transmitPower = channelSettings.lowpower === 0 ? 5 : 1;
@@ -74,12 +155,15 @@ export function bindingsToChannels(memoryMap, settings) {
74
155
  if (typeof channelSettings.scan === 'boolean') {
75
156
  channelSettings.skip = channelSettings.scan ? '' : 'S';
76
157
  }
158
+ if (typeof channelSettings.skip === 'boolean') {
159
+ channelSettings.skip = channelSettings.skip ? 'S' : '';
160
+ }
77
161
  const radioChannel = {
78
162
  name,
79
163
  receiveFrequency: Frequency(receiveFrequency),
80
164
  transmitFrequency: Frequency(transmitFrequency),
81
- receiveTone: toneToRadioTone(record[bindings.receiveTone]),
82
- transmitTone: toneToRadioTone(record[bindings.transmitTone]),
165
+ receiveTone: tones.receiveTone,
166
+ transmitTone: tones.transmitTone,
83
167
  };
84
168
  channels.push({
85
169
  channelNumber: index,
@@ -103,6 +187,9 @@ export function settingsWithoutChannels(memoryMap, settings) {
103
187
  if (bindings.names) {
104
188
  delete next[bindings.names];
105
189
  }
190
+ if (bindings.extras) {
191
+ delete next[bindings.extras];
192
+ }
106
193
  return next;
107
194
  }
108
195
  /**
@@ -115,10 +202,12 @@ export function programToChannelSettings(memoryMap, program) {
115
202
  }
116
203
  const recordsStruct = memoryMap.structs.find((struct) => struct.id === bindings.records);
117
204
  const namesStruct = bindings.names ? memoryMap.structs.find((struct) => struct.id === bindings.names) : undefined;
205
+ const extrasStruct = bindings.extras ? memoryMap.structs.find((struct) => struct.id === bindings.extras) : undefined;
118
206
  const count = recordsStruct?.count ?? 0;
119
207
  const nameField = bindings.nameField ?? 'name';
120
208
  const records = Array.from({ length: count }, () => null);
121
209
  const names = Array.from({ length: count }, () => null);
210
+ const extras = Array.from({ length: count }, () => null);
122
211
  for (const programmed of program.channels) {
123
212
  const index = programmed.channelNumber;
124
213
  if (index < 0 || index >= count) {
@@ -129,12 +218,61 @@ export function programToChannelSettings(memoryMap, program) {
129
218
  }
130
219
  const channel = programmed.radioChannel;
131
220
  const channelSettings = programmed.settings ?? {};
221
+ const receiveFrequency = Number(channel.receiveFrequency);
222
+ const transmitFrequency = Number(channel.transmitFrequency);
223
+ let offset = transmitFrequency;
224
+ let duplex = '';
225
+ let split = false;
226
+ if (transmitFrequency === receiveFrequency) {
227
+ offset = 0;
228
+ duplex = '';
229
+ }
230
+ else if (transmitFrequency > receiveFrequency) {
231
+ offset = transmitFrequency - receiveFrequency;
232
+ duplex = '+';
233
+ }
234
+ else {
235
+ offset = receiveFrequency - transmitFrequency;
236
+ duplex = '-';
237
+ }
238
+ if (channelSettings.split === true) {
239
+ split = true;
240
+ duplex = '';
241
+ offset = transmitFrequency;
242
+ }
243
+ const usesOffset = 'duplex' in channelSettings || extrasStruct || structHasField(recordsStruct, 'duplex');
244
+ const usesKenwoodToneBits = 'tone_mode' in channelSettings ||
245
+ 'ctcss_mode' in channelSettings ||
246
+ extrasStruct ||
247
+ structHasField(recordsStruct, 'tone_mode') ||
248
+ structHasField(recordsStruct, 'ctcss_mode');
132
249
  const record = {
133
- [bindings.receiveFrequency]: channel.receiveFrequency,
134
- [bindings.transmitFrequency]: channel.transmitFrequency,
250
+ [bindings.receiveFrequency]: receiveFrequency,
251
+ [bindings.transmitFrequency]: usesOffset ? offset : transmitFrequency,
135
252
  [bindings.receiveTone]: radioToneToMapTone(channel.receiveTone),
136
253
  [bindings.transmitTone]: radioToneToMapTone(channel.transmitTone),
137
254
  };
255
+ if (usesOffset) {
256
+ record.duplex = typeof channelSettings.duplex === 'string' ? channelSettings.duplex : duplex;
257
+ record.split = typeof channelSettings.split === 'boolean' ? channelSettings.split : split;
258
+ record.offset = offset;
259
+ }
260
+ if (usesKenwoodToneBits) {
261
+ record.tone_mode = false;
262
+ record.ctcss_mode = false;
263
+ record.dtcs_mode = false;
264
+ record.cross_mode = false;
265
+ if (channel.transmitTone?.type === RadioToneType.DCS || channel.receiveTone?.type === RadioToneType.DCS) {
266
+ record.dtcs_mode = true;
267
+ record.dtcs_code = radioToneToMapTone(channel.transmitTone?.type === RadioToneType.DCS ? channel.transmitTone : channel.receiveTone);
268
+ }
269
+ else if (!isNoneTone(channel.receiveTone) && !isNoneTone(channel.transmitTone)) {
270
+ record.ctcss_mode = true;
271
+ }
272
+ else if (!isNoneTone(channel.transmitTone)) {
273
+ record.tone_mode = true;
274
+ }
275
+ }
138
276
  for (const [key, value] of Object.entries(channelSettings)) {
139
277
  if (key === 'transmitPower') {
140
278
  record.lowpower = value === 5 || value === 4 || value === 0 ? 0 : 1;
@@ -142,10 +280,12 @@ export function programToChannelSettings(memoryMap, program) {
142
280
  }
143
281
  if (key === 'mode') {
144
282
  record.wide = value === 'FM';
283
+ record.mode = value;
145
284
  continue;
146
285
  }
147
286
  if (key === 'skip') {
148
287
  record.scan = value !== 'S';
288
+ record.skip = value === 'S';
149
289
  continue;
150
290
  }
151
291
  record[key] = value;
@@ -160,12 +300,27 @@ export function programToChannelSettings(memoryMap, program) {
160
300
  record.scan = true;
161
301
  }
162
302
  records[index] = record;
163
- names[index] = { [nameField]: channel.name ?? '' };
303
+ if (namesStruct) {
304
+ names[index] = { [nameField]: channel.name ?? '' };
305
+ }
306
+ else {
307
+ record[nameField] = channel.name ?? '';
308
+ }
309
+ if (extrasStruct) {
310
+ extras[index] = {
311
+ used: kenwoodUsedFlag(receiveFrequency, transmitFrequency),
312
+ lockout: channelSettings.skip === 'S' || channelSettings.lockout === true,
313
+ group: typeof channelSettings.group === 'number' ? channelSettings.group : 0,
314
+ };
315
+ }
164
316
  }
165
317
  const bag = { ...program.settings, [bindings.records]: records };
166
318
  if (bindings.names && namesStruct) {
167
319
  bag[bindings.names] = names;
168
320
  }
321
+ if (bindings.extras && extrasStruct) {
322
+ bag[bindings.extras] = extras;
323
+ }
169
324
  return bag;
170
325
  }
171
326
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"memory-map-channels.js","sourceRoot":"","sources":["../../src/memory/memory-map-channels.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Frequency,\n type RadioChannel,\n type RadioMemoryConfig,\n type RadioMemoryMap,\n type RadioMemoryMapToneValue,\n type RadioProgram,\n type RadioProgrammedChannel,\n type RadioSettings,\n type RadioSettingValue,\n type RadioTone,\n RadioToneType,\n} from '@springfield/ham-radio-api';\nimport { decodeMemoryMap, encodeMemoryMap } from './memory-map-codec.js';\n\nfunction isToneValue(value: RadioSettingValue | undefined): value is RadioMemoryMapToneValue {\n return Boolean(value && typeof value === 'object' && !Array.isArray(value) && 'mode' in value);\n}\n\nfunction toneToRadioTone(value: RadioSettingValue | undefined): RadioTone {\n if (!isToneValue(value) || value.mode === 'none') {\n return { tone: 0, type: RadioToneType.CTCSS };\n }\n\n if (value.mode === 'ctcss') {\n return { tone: value.value, type: RadioToneType.CTCSS };\n }\n\n return { tone: value.code, type: RadioToneType.DCS };\n}\n\nfunction radioToneToMapTone(tone: RadioTone | undefined): RadioMemoryMapToneValue {\n if (!tone || !tone.tone) {\n return { mode: 'none' };\n }\n\n if (tone.type === RadioToneType.DCS) {\n return { mode: 'dcs', code: Number(tone.tone), polarity: 'N' };\n }\n\n return { mode: 'ctcss', value: Number(tone.tone) };\n}\n\nfunction asRecord(value: RadioSettingValue | undefined): Record<string, RadioSettingValue> | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n\n return value as Record<string, RadioSettingValue>;\n}\n\n/**\n * Project decoded memory-map channel structs into RadioProgrammedChannel[].\n */\nexport function bindingsToChannels(\n memoryMap: RadioMemoryMap,\n settings: RadioSettings,\n): RadioProgrammedChannel[] {\n const bindings = memoryMap.channelBindings;\n\n if (!bindings) {\n return [];\n }\n\n const records = settings[bindings.records];\n const names = bindings.names ? settings[bindings.names] : undefined;\n const recordItems = Array.isArray(records) ? records : [];\n const nameItems = Array.isArray(names) ? names : [];\n const nameField = bindings.nameField ?? 'name';\n const boundFieldIds = new Set([\n bindings.receiveFrequency,\n bindings.transmitFrequency,\n bindings.receiveTone,\n bindings.transmitTone,\n ]);\n\n const channels: RadioProgrammedChannel[] = [];\n\n for (let index = 0; index < recordItems.length; index += 1) {\n const record = asRecord(recordItems[index]);\n\n if (!record) {\n continue;\n }\n\n const nameRecord = asRecord(nameItems[index]);\n const nameValue = nameRecord?.[nameField];\n const name = typeof nameValue === 'string' ? nameValue : '';\n\n const receiveFrequency = Number(record[bindings.receiveFrequency] ?? 0);\n const transmitFrequency = Number(record[bindings.transmitFrequency] ?? 0);\n\n const channelSettings: RadioSettings = {};\n\n for (const [fieldId, fieldValue] of Object.entries(record)) {\n if (boundFieldIds.has(fieldId) || fieldId.startsWith('_')) {\n continue;\n }\n\n channelSettings[fieldId] = fieldValue;\n }\n\n // Map Chirp lowpower index onto legacy transmitPower watts for UV-5R UI compatibility.\n if (typeof channelSettings.lowpower === 'number') {\n channelSettings.transmitPower = channelSettings.lowpower === 0 ? 5 : 1;\n }\n\n if (typeof channelSettings.wide === 'boolean') {\n channelSettings.mode = channelSettings.wide ? 'FM' : 'NFM';\n }\n\n if (typeof channelSettings.scan === 'boolean') {\n channelSettings.skip = channelSettings.scan ? '' : 'S';\n }\n\n const radioChannel: RadioChannel = {\n name,\n receiveFrequency: Frequency(receiveFrequency),\n transmitFrequency: Frequency(transmitFrequency),\n receiveTone: toneToRadioTone(record[bindings.receiveTone]),\n transmitTone: toneToRadioTone(record[bindings.transmitTone]),\n };\n\n channels.push({\n channelNumber: index,\n radioChannel,\n settings: Object.keys(channelSettings).length > 0 ? channelSettings : undefined,\n });\n }\n\n return channels;\n}\n\n/**\n * Strip channel-bound structs from decoded settings so RadioProgram.settings\n * only contains radio-wide settings.\n */\nexport function settingsWithoutChannels(memoryMap: RadioMemoryMap, settings: RadioSettings): RadioSettings {\n const bindings = memoryMap.channelBindings;\n\n if (!bindings) {\n return settings;\n }\n\n const next: RadioSettings = { ...settings };\n delete next[bindings.records];\n\n if (bindings.names) {\n delete next[bindings.names];\n }\n\n return next;\n}\n\n/**\n * Merge RadioProgram channels back into a memory-map settings bag for encode.\n */\nexport function programToChannelSettings(memoryMap: RadioMemoryMap, program: RadioProgram): RadioSettings {\n const bindings = memoryMap.channelBindings;\n\n if (!bindings) {\n return program.settings;\n }\n\n const recordsStruct = memoryMap.structs.find((struct) => struct.id === bindings.records);\n const namesStruct = bindings.names ? memoryMap.structs.find((struct) => struct.id === bindings.names) : undefined;\n const count = recordsStruct?.count ?? 0;\n const nameField = bindings.nameField ?? 'name';\n\n const records: RadioSettingValue[] = Array.from({ length: count }, () => null);\n const names: RadioSettingValue[] = Array.from({ length: count }, () => null);\n\n for (const programmed of program.channels) {\n const index = programmed.channelNumber;\n\n if (index < 0 || index >= count) {\n continue;\n }\n\n if (typeof programmed.radioChannel === 'string') {\n continue;\n }\n\n const channel = programmed.radioChannel;\n const channelSettings = programmed.settings ?? {};\n const record: Record<string, RadioSettingValue> = {\n [bindings.receiveFrequency]: channel.receiveFrequency,\n [bindings.transmitFrequency]: channel.transmitFrequency,\n [bindings.receiveTone]: radioToneToMapTone(channel.receiveTone),\n [bindings.transmitTone]: radioToneToMapTone(channel.transmitTone),\n };\n\n for (const [key, value] of Object.entries(channelSettings)) {\n if (key === 'transmitPower') {\n record.lowpower = value === 5 || value === 4 || value === 0 ? 0 : 1;\n continue;\n }\n\n if (key === 'mode') {\n record.wide = value === 'FM';\n continue;\n }\n\n if (key === 'skip') {\n record.scan = value !== 'S';\n continue;\n }\n\n record[key] = value;\n }\n\n if (record.lowpower === undefined) {\n record.lowpower = 0;\n }\n\n if (record.wide === undefined) {\n record.wide = true;\n }\n\n if (record.scan === undefined) {\n record.scan = true;\n }\n\n records[index] = record;\n names[index] = { [nameField]: channel.name ?? '' };\n }\n\n const bag: RadioSettings = { ...program.settings, [bindings.records]: records };\n\n if (bindings.names && namesStruct) {\n bag[bindings.names] = names;\n }\n\n return bag;\n}\n\n/**\n * Decode a full RadioProgram (channels + radio-wide settings) from memory.\n */\nexport function decodeRadioProgram(\n memoryMap: RadioMemoryMap,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n): RadioProgram {\n const decoded = decodeMemoryMap(memoryMap, contents, memoryConfig);\n\n return {\n channels: bindingsToChannels(memoryMap, decoded),\n settings: settingsWithoutChannels(memoryMap, decoded),\n };\n}\n\n/**\n * Encode a RadioProgram into an existing memory image (patch in place).\n */\nexport function encodeRadioProgram(\n memoryMap: RadioMemoryMap,\n program: RadioProgram,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n): Uint8Array {\n const bag = programToChannelSettings(memoryMap, program);\n return encodeMemoryMap(memoryMap, bag, contents, memoryConfig);\n}\n"]}
1
+ {"version":3,"file":"memory-map-channels.js","sourceRoot":"","sources":["../../src/memory/memory-map-channels.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Frequency,\n type RadioChannel,\n type RadioMemoryConfig,\n type RadioMemoryMap,\n type RadioMemoryMapToneValue,\n type RadioProgram,\n type RadioProgrammedChannel,\n type RadioSettings,\n type RadioSettingValue,\n type RadioTone,\n RadioToneType,\n} from '@springfield/ham-radio-api';\nimport { decodeMemoryMap, encodeMemoryMap } from './memory-map-codec.js';\n\nfunction isToneValue(value: RadioSettingValue | undefined): value is RadioMemoryMapToneValue {\n return Boolean(value && typeof value === 'object' && !Array.isArray(value) && 'mode' in value);\n}\n\nfunction toneToRadioTone(value: RadioSettingValue | undefined): RadioTone {\n if (!isToneValue(value) || value.mode === 'none') {\n return { tone: 0, type: RadioToneType.CTCSS };\n }\n\n if (value.mode === 'ctcss') {\n return { tone: value.value, type: RadioToneType.CTCSS };\n }\n\n return { tone: value.code, type: RadioToneType.DCS };\n}\n\nfunction radioToneToMapTone(tone: RadioTone | undefined): RadioMemoryMapToneValue {\n if (!tone || !tone.tone) {\n return { mode: 'none' };\n }\n\n if (tone.type === RadioToneType.DCS) {\n return { mode: 'dcs', code: Number(tone.tone), polarity: 'N' };\n }\n\n return { mode: 'ctcss', value: Number(tone.tone) };\n}\n\nfunction noneTone(): RadioTone {\n return { tone: 0, type: RadioToneType.CTCSS };\n}\n\nfunction isNoneTone(tone: RadioTone | undefined): boolean {\n return !tone || !tone.tone;\n}\n\nfunction structHasField(struct: { fields: { id: string }[] } | undefined, fieldId: string): boolean {\n return Boolean(struct?.fields.some((field) => field.id === fieldId));\n}\n\nfunction transmitFrequencyFromRecord(record: Record<string, RadioSettingValue>, bindings: NonNullable<RadioMemoryMap['channelBindings']>): number {\n const receiveFrequency = Number(record[bindings.receiveFrequency] ?? 0);\n const offsetOrTransmit = Number(record[bindings.transmitFrequency] ?? 0);\n\n if (record.split === true) {\n return offsetOrTransmit;\n }\n\n if (record.duplex === '+') {\n return receiveFrequency + offsetOrTransmit;\n }\n\n if (record.duplex === '-') {\n return receiveFrequency - offsetOrTransmit;\n }\n\n // Kenwood stores simplex as duplex \"\" and offset 0. Empty duplex is not an absolute TX frequency.\n if (record.duplex === '') {\n return receiveFrequency;\n }\n\n if (record.duplex === undefined) {\n return bindings.transmitFrequency === bindings.receiveFrequency ? receiveFrequency : offsetOrTransmit;\n }\n\n return offsetOrTransmit;\n}\n\nfunction applyKenwoodToneMode(\n record: Record<string, RadioSettingValue>,\n bindings: NonNullable<RadioMemoryMap['channelBindings']>,\n): { receiveTone: RadioTone; transmitTone: RadioTone } {\n const hasToneModeBits = 'tone_mode' in record || 'ctcss_mode' in record || 'dtcs_mode' in record;\n\n if (!hasToneModeBits) {\n return {\n receiveTone: toneToRadioTone(record[bindings.receiveTone]),\n transmitTone: toneToRadioTone(record[bindings.transmitTone]),\n };\n }\n\n if (record.tone_mode === true) {\n return { receiveTone: noneTone(), transmitTone: toneToRadioTone(record[bindings.transmitTone]) };\n }\n\n if (record.ctcss_mode === true) {\n const tone = toneToRadioTone(record[bindings.receiveTone]);\n return { receiveTone: tone, transmitTone: tone };\n }\n\n if (record.dtcs_mode === true) {\n const tone = toneToRadioTone(record.dtcs_code);\n return { receiveTone: tone, transmitTone: tone };\n }\n\n return { receiveTone: noneTone(), transmitTone: noneTone() };\n}\n\nfunction kenwoodUsedFlag(receiveFrequency: number, transmitFrequency: number): number {\n const frequency = transmitFrequency || receiveFrequency;\n\n if (frequency < 150_000_000) {\n return 0;\n }\n\n if (frequency < 400_000_000) {\n return 1;\n }\n\n return 2;\n}\n\nfunction asRecord(value: RadioSettingValue | undefined): Record<string, RadioSettingValue> | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n\n return value as Record<string, RadioSettingValue>;\n}\n\n/**\n * Project decoded memory-map channel structs into RadioProgrammedChannel[].\n */\nexport function bindingsToChannels(\n memoryMap: RadioMemoryMap,\n settings: RadioSettings,\n): RadioProgrammedChannel[] {\n const bindings = memoryMap.channelBindings;\n\n if (!bindings) {\n return [];\n }\n\n const records = settings[bindings.records];\n const names = bindings.names ? settings[bindings.names] : undefined;\n const extras = bindings.extras ? settings[bindings.extras] : undefined;\n const recordItems = Array.isArray(records) ? records : [];\n const nameItems = Array.isArray(names) ? names : [];\n const extraItems = Array.isArray(extras) ? extras : [];\n const nameField = bindings.nameField ?? 'name';\n const boundFieldIds = new Set([\n bindings.receiveFrequency,\n bindings.transmitFrequency,\n bindings.receiveTone,\n bindings.transmitTone,\n nameField,\n ]);\n\n const channels: RadioProgrammedChannel[] = [];\n\n for (let index = 0; index < recordItems.length; index += 1) {\n const record = asRecord(recordItems[index]);\n const extraRecord = asRecord(extraItems[index]);\n\n if (bindings.extras && extraItems.length > 0 && extraRecord === undefined) {\n continue;\n }\n\n if (!record) {\n continue;\n }\n\n const nameRecord = asRecord(nameItems[index]);\n const nameFromTable = typeof nameRecord?.[nameField] === 'string' ? nameRecord[nameField] : '';\n const nameFromRecord = typeof record[nameField] === 'string' ? record[nameField] : '';\n const name = nameFromTable || nameFromRecord;\n\n const receiveFrequency = Number(record[bindings.receiveFrequency] ?? 0);\n const transmitFrequency = transmitFrequencyFromRecord(record, bindings);\n const tones = applyKenwoodToneMode(record, bindings);\n\n const channelSettings: RadioSettings = {};\n\n for (const [fieldId, fieldValue] of Object.entries(record)) {\n if (boundFieldIds.has(fieldId) || fieldId.startsWith('_')) {\n continue;\n }\n\n channelSettings[fieldId] = fieldValue;\n }\n\n if (extraRecord) {\n for (const [fieldId, fieldValue] of Object.entries(extraRecord)) {\n if (fieldId.startsWith('_') || fieldId === 'used') {\n continue;\n }\n\n channelSettings[fieldId] = fieldValue;\n }\n\n if (typeof extraRecord.lockout === 'boolean') {\n channelSettings.skip = extraRecord.lockout ? 'S' : '';\n }\n }\n\n // Map Chirp lowpower index onto legacy transmitPower watts for UV-5R UI compatibility.\n if (typeof channelSettings.lowpower === 'number') {\n channelSettings.transmitPower = channelSettings.lowpower === 0 ? 5 : 1;\n }\n\n if (typeof channelSettings.wide === 'boolean') {\n channelSettings.mode = channelSettings.wide ? 'FM' : 'NFM';\n }\n\n if (typeof channelSettings.scan === 'boolean') {\n channelSettings.skip = channelSettings.scan ? '' : 'S';\n }\n\n if (typeof channelSettings.skip === 'boolean') {\n channelSettings.skip = channelSettings.skip ? 'S' : '';\n }\n\n const radioChannel: RadioChannel = {\n name,\n receiveFrequency: Frequency(receiveFrequency),\n transmitFrequency: Frequency(transmitFrequency),\n receiveTone: tones.receiveTone,\n transmitTone: tones.transmitTone,\n };\n\n channels.push({\n channelNumber: index,\n radioChannel,\n settings: Object.keys(channelSettings).length > 0 ? channelSettings : undefined,\n });\n }\n\n return channels;\n}\n\n/**\n * Strip channel-bound structs from decoded settings so RadioProgram.settings\n * only contains radio-wide settings.\n */\nexport function settingsWithoutChannels(memoryMap: RadioMemoryMap, settings: RadioSettings): RadioSettings {\n const bindings = memoryMap.channelBindings;\n\n if (!bindings) {\n return settings;\n }\n\n const next: RadioSettings = { ...settings };\n delete next[bindings.records];\n\n if (bindings.names) {\n delete next[bindings.names];\n }\n\n if (bindings.extras) {\n delete next[bindings.extras];\n }\n\n return next;\n}\n\n/**\n * Merge RadioProgram channels back into a memory-map settings bag for encode.\n */\nexport function programToChannelSettings(memoryMap: RadioMemoryMap, program: RadioProgram): RadioSettings {\n const bindings = memoryMap.channelBindings;\n\n if (!bindings) {\n return program.settings;\n }\n\n const recordsStruct = memoryMap.structs.find((struct) => struct.id === bindings.records);\n const namesStruct = bindings.names ? memoryMap.structs.find((struct) => struct.id === bindings.names) : undefined;\n const extrasStruct = bindings.extras ? memoryMap.structs.find((struct) => struct.id === bindings.extras) : undefined;\n const count = recordsStruct?.count ?? 0;\n const nameField = bindings.nameField ?? 'name';\n\n const records: RadioSettingValue[] = Array.from({ length: count }, () => null);\n const names: RadioSettingValue[] = Array.from({ length: count }, () => null);\n const extras: RadioSettingValue[] = Array.from({ length: count }, () => null);\n\n for (const programmed of program.channels) {\n const index = programmed.channelNumber;\n\n if (index < 0 || index >= count) {\n continue;\n }\n\n if (typeof programmed.radioChannel === 'string') {\n continue;\n }\n\n const channel = programmed.radioChannel;\n const channelSettings = programmed.settings ?? {};\n const receiveFrequency = Number(channel.receiveFrequency);\n const transmitFrequency = Number(channel.transmitFrequency);\n let offset = transmitFrequency;\n let duplex: RadioSettingValue = '';\n let split = false;\n\n if (transmitFrequency === receiveFrequency) {\n offset = 0;\n duplex = '';\n } else if (transmitFrequency > receiveFrequency) {\n offset = transmitFrequency - receiveFrequency;\n duplex = '+';\n } else {\n offset = receiveFrequency - transmitFrequency;\n duplex = '-';\n }\n\n if (channelSettings.split === true) {\n split = true;\n duplex = '';\n offset = transmitFrequency;\n }\n\n const usesOffset =\n 'duplex' in channelSettings || extrasStruct || structHasField(recordsStruct, 'duplex');\n const usesKenwoodToneBits =\n 'tone_mode' in channelSettings ||\n 'ctcss_mode' in channelSettings ||\n extrasStruct ||\n structHasField(recordsStruct, 'tone_mode') ||\n structHasField(recordsStruct, 'ctcss_mode');\n\n const record: Record<string, RadioSettingValue> = {\n [bindings.receiveFrequency]: receiveFrequency,\n [bindings.transmitFrequency]: usesOffset ? offset : transmitFrequency,\n [bindings.receiveTone]: radioToneToMapTone(channel.receiveTone),\n [bindings.transmitTone]: radioToneToMapTone(channel.transmitTone),\n };\n\n if (usesOffset) {\n record.duplex = typeof channelSettings.duplex === 'string' ? channelSettings.duplex : duplex;\n record.split = typeof channelSettings.split === 'boolean' ? channelSettings.split : split;\n record.offset = offset;\n }\n\n if (usesKenwoodToneBits) {\n record.tone_mode = false;\n record.ctcss_mode = false;\n record.dtcs_mode = false;\n record.cross_mode = false;\n\n if (channel.transmitTone?.type === RadioToneType.DCS || channel.receiveTone?.type === RadioToneType.DCS) {\n record.dtcs_mode = true;\n record.dtcs_code = radioToneToMapTone(channel.transmitTone?.type === RadioToneType.DCS ? channel.transmitTone : channel.receiveTone);\n } else if (!isNoneTone(channel.receiveTone) && !isNoneTone(channel.transmitTone)) {\n record.ctcss_mode = true;\n } else if (!isNoneTone(channel.transmitTone)) {\n record.tone_mode = true;\n }\n }\n\n for (const [key, value] of Object.entries(channelSettings)) {\n if (key === 'transmitPower') {\n record.lowpower = value === 5 || value === 4 || value === 0 ? 0 : 1;\n continue;\n }\n\n if (key === 'mode') {\n record.wide = value === 'FM';\n record.mode = value;\n continue;\n }\n\n if (key === 'skip') {\n record.scan = value !== 'S';\n record.skip = value === 'S';\n continue;\n }\n\n record[key] = value;\n }\n\n if (record.lowpower === undefined) {\n record.lowpower = 0;\n }\n\n if (record.wide === undefined) {\n record.wide = true;\n }\n\n if (record.scan === undefined) {\n record.scan = true;\n }\n\n records[index] = record;\n\n if (namesStruct) {\n names[index] = { [nameField]: channel.name ?? '' };\n } else {\n record[nameField] = channel.name ?? '';\n }\n\n if (extrasStruct) {\n extras[index] = {\n used: kenwoodUsedFlag(receiveFrequency, transmitFrequency),\n lockout: channelSettings.skip === 'S' || channelSettings.lockout === true,\n group: typeof channelSettings.group === 'number' ? channelSettings.group : 0,\n };\n }\n }\n\n const bag: RadioSettings = { ...program.settings, [bindings.records]: records };\n\n if (bindings.names && namesStruct) {\n bag[bindings.names] = names;\n }\n\n if (bindings.extras && extrasStruct) {\n bag[bindings.extras] = extras;\n }\n\n return bag;\n}\n\n/**\n * Decode a full RadioProgram (channels + radio-wide settings) from memory.\n */\nexport function decodeRadioProgram(\n memoryMap: RadioMemoryMap,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n): RadioProgram {\n const decoded = decodeMemoryMap(memoryMap, contents, memoryConfig);\n\n return {\n channels: bindingsToChannels(memoryMap, decoded),\n settings: settingsWithoutChannels(memoryMap, decoded),\n };\n}\n\n/**\n * Encode a RadioProgram into an existing memory image (patch in place).\n */\nexport function encodeRadioProgram(\n memoryMap: RadioMemoryMap,\n program: RadioProgram,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n): Uint8Array {\n const bag = programToChannelSettings(memoryMap, program);\n return encodeMemoryMap(memoryMap, bag, contents, memoryConfig);\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { RadioMemoryConfig, RadioMemoryMap, RadioSettings } from '@springfield/ham-radio-api';
1
+ import type { RadioMemoryConfig, RadioMemoryMap, RadioMemoryMapStruct, RadioSettings } from '@springfield/ham-radio-api';
2
2
  /**
3
3
  * Parse a seek address from a number or hex/decimal string.
4
4
  */
@@ -10,6 +10,11 @@ export declare function parseSeekAddress(seek: number | string): number;
10
10
  * Sparse buffers that cover the highest segment end address use absolute offsets.
11
11
  */
12
12
  export declare function radioAddressToBufferOffset(radioAddress: number, memoryConfig: RadioMemoryConfig, bufferLength: number): number;
13
+ /**
14
+ * Radio address of repeated-struct instance `index`, including Kenwood-style
15
+ * grouped stride (N records + pad bytes per clone block).
16
+ */
17
+ export declare function structInstanceAddress(struct: RadioMemoryMapStruct, instanceIndex: number): number;
13
18
  /**
14
19
  * Decode radio-wide settings from a memory image using a JSON memory map.
15
20
  */
@@ -1 +1 @@
1
- {"version":3,"file":"memory-map-codec.d.ts","sourceRoot":"","sources":["../../src/memory/memory-map-codec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EAId,aAAa,EAEd,MAAM,4BAA4B,CAAC;AAIpC;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAY9D;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,iBAAiB,EAC/B,YAAY,EAAE,MAAM,GACnB,MAAM,CAmBR;AA8gBD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,iBAAiB,GAC9B,aAAa,CAwBf;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,aAAa,EACvB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,iBAAiB,GAC9B,UAAU,CA+BZ"}
1
+ {"version":3,"file":"memory-map-codec.d.ts","sourceRoot":"","sources":["../../src/memory/memory-map-codec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EAEd,oBAAoB,EAEpB,aAAa,EAEd,MAAM,4BAA4B,CAAC;AAIpC;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAY9D;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,iBAAiB,EAC/B,YAAY,EAAE,MAAM,GACnB,MAAM,CAmBR;AAgDD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAcjG;AA8gBD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,iBAAiB,GAC9B,aAAa,CAwBf;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,aAAa,EACvB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,iBAAiB,GAC9B,UAAU,CA+BZ"}
@@ -48,13 +48,48 @@ function writeByte(contents, offset, value) {
48
48
  }
49
49
  contents[offset] = value & 0xff;
50
50
  }
51
+ function decodeInteger(raw) {
52
+ if (typeof raw === 'number') {
53
+ return raw;
54
+ }
55
+ let value = 0;
56
+ for (let index = 0; index < raw.length; index += 1) {
57
+ value += raw[index] * 256 ** index;
58
+ }
59
+ return value;
60
+ }
61
+ function encodeInteger(value, byteLength) {
62
+ let numeric = typeof value === 'number' ? value : 0;
63
+ const bytes = [];
64
+ for (let index = 0; index < byteLength; index += 1) {
65
+ bytes.push(numeric & 0xff);
66
+ numeric = Math.floor(numeric / 256);
67
+ }
68
+ return bytes;
69
+ }
70
+ /**
71
+ * Radio address of repeated-struct instance `index`, including Kenwood-style
72
+ * grouped stride (N records + pad bytes per clone block).
73
+ */
74
+ export function structInstanceAddress(struct, instanceIndex) {
75
+ const seek = parseSeekAddress(struct.seek);
76
+ const stride = struct.stride ?? 0;
77
+ const groupSize = struct.groupSize;
78
+ if (!groupSize) {
79
+ return seek + instanceIndex * stride;
80
+ }
81
+ const groupPad = struct.groupPad ?? 0;
82
+ const groupIndex = Math.floor(instanceIndex / groupSize);
83
+ const indexInGroup = instanceIndex % groupSize;
84
+ return seek + groupIndex * (stride * groupSize + groupPad) + indexInGroup * stride;
85
+ }
51
86
  function decodeRawValue(kind, raw) {
52
87
  if (!kind) {
53
88
  return typeof raw === 'number' ? raw : raw;
54
89
  }
55
90
  switch (kind.kind) {
56
91
  case 'integer':
57
- return typeof raw === 'number' ? raw : raw[0];
92
+ return decodeInteger(raw);
58
93
  case 'boolean':
59
94
  return (typeof raw === 'number' ? raw : raw[0]) !== 0;
60
95
  case 'enum': {
@@ -135,6 +170,16 @@ function decodeRawValue(kind, raw) {
135
170
  const code = kind.values[index] ?? 0;
136
171
  return { mode: 'dcs', code, polarity };
137
172
  }
173
+ case 'ctcss-index': {
174
+ const index = decodeInteger(raw);
175
+ const tenths = kind.values[index] ?? kind.values[0] ?? 0;
176
+ return { mode: 'ctcss', value: tenths };
177
+ }
178
+ case 'dcs-index': {
179
+ const index = decodeInteger(raw);
180
+ const code = kind.values[index] ?? 0;
181
+ return { mode: 'dcs', code, polarity: 'N' };
182
+ }
138
183
  default: {
139
184
  const exhaustive = kind;
140
185
  return exhaustive;
@@ -151,8 +196,7 @@ function encodeRawValue(kind, value, byteLength) {
151
196
  }
152
197
  switch (kind.kind) {
153
198
  case 'integer': {
154
- bytes[0] = typeof value === 'number' ? value & 0xff : 0;
155
- return bytes;
199
+ return encodeInteger(value, byteLength);
156
200
  }
157
201
  case 'boolean': {
158
202
  bytes[0] = value ? 1 : 0;
@@ -165,8 +209,9 @@ function encodeRawValue(kind, value, byteLength) {
165
209
  }
166
210
  case 'ascii': {
167
211
  const text = typeof value === 'string' ? value : '';
212
+ const pad = kind.pad ?? 0xff;
168
213
  for (let index = 0; index < byteLength; index += 1) {
169
- bytes[index] = index < text.length ? (text.codePointAt(index) ?? 0xff) : 0xff;
214
+ bytes[index] = index < text.length ? (text.codePointAt(index) ?? pad) : pad;
170
215
  }
171
216
  return bytes;
172
217
  }
@@ -242,6 +287,18 @@ function encodeRawValue(kind, value, byteLength) {
242
287
  bytes[1] = (rawValue >> 8) & 0xff;
243
288
  return bytes;
244
289
  }
290
+ case 'ctcss-index': {
291
+ const tone = value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
292
+ const tenths = tone?.mode === 'ctcss' && typeof tone.value === 'number' ? tone.value : 0;
293
+ const index = kind.values.indexOf(tenths);
294
+ return encodeInteger(index >= 0 ? index : 0, byteLength);
295
+ }
296
+ case 'dcs-index': {
297
+ const tone = value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
298
+ const code = tone?.mode === 'dcs' && typeof tone.code === 'number' ? tone.code : 0;
299
+ const index = kind.values.indexOf(code);
300
+ return encodeInteger(index >= 0 ? index : 0, byteLength);
301
+ }
245
302
  default: {
246
303
  const exhaustive = kind;
247
304
  return exhaustive;
@@ -249,6 +306,9 @@ function encodeRawValue(kind, value, byteLength) {
249
306
  }
250
307
  }
251
308
  function fieldByteLength(field) {
309
+ if (field.type === 'u32') {
310
+ return 4;
311
+ }
252
312
  if (field.type === 'u16') {
253
313
  return 2;
254
314
  }
@@ -338,7 +398,7 @@ class BitfieldCursor {
338
398
  }
339
399
  }
340
400
  function decodeStruct(struct, contents, memoryConfig, instanceIndex) {
341
- const base = parseSeekAddress(struct.seek) + instanceIndex * (struct.stride ?? 0);
401
+ const base = structInstanceAddress(struct, instanceIndex);
342
402
  const result = {};
343
403
  let radioAddress = base;
344
404
  let bitCursor;
@@ -362,7 +422,7 @@ function decodeStruct(struct, contents, memoryConfig, instanceIndex) {
362
422
  for (let index = 0; index < length; index += 1) {
363
423
  bytes.push(readByte(contents, bufferOffsetFor(radioAddress + index, memoryConfig, contents)));
364
424
  }
365
- if (field.type === 'u16') {
425
+ if (field.type === 'u16' || field.type === 'u32') {
366
426
  if (!field.reserved) {
367
427
  result[field.id] = decodeRawValue(field.value ?? { kind: 'integer' }, bytes);
368
428
  }
@@ -375,7 +435,7 @@ function decodeStruct(struct, contents, memoryConfig, instanceIndex) {
375
435
  return result;
376
436
  }
377
437
  function encodeStruct(struct, values, contents, memoryConfig, instanceIndex) {
378
- const base = parseSeekAddress(struct.seek) + instanceIndex * (struct.stride ?? 0);
438
+ const base = structInstanceAddress(struct, instanceIndex);
379
439
  let radioAddress = base;
380
440
  let bitCursor;
381
441
  let bitStartAddress = base;
@@ -406,17 +466,11 @@ function encodeStruct(struct, values, contents, memoryConfig, instanceIndex) {
406
466
  continue;
407
467
  }
408
468
  const settingValue = values[field.id];
409
- if (field.type === 'u16') {
410
- const encoded = encodeRawValue(field.value ?? { kind: 'integer' }, settingValue ?? 0, 2);
411
- let value = 0;
412
- if (field.value?.kind === 'tone') {
413
- value = (encoded[0] & 0xff) | ((encoded[1] & 0xff) << 8);
414
- }
415
- else {
416
- value = (encoded[0] ?? 0) & 0xffff;
469
+ if (field.type === 'u16' || field.type === 'u32') {
470
+ const encoded = encodeRawValue(field.value ?? { kind: 'integer' }, settingValue ?? 0, length);
471
+ for (let index = 0; index < length; index += 1) {
472
+ writeByte(contents, bufferOffsetFor(radioAddress + index, memoryConfig, contents), encoded[index] ?? 0);
417
473
  }
418
- writeByte(contents, bufferOffsetFor(radioAddress, memoryConfig, contents), value & 0xff);
419
- writeByte(contents, bufferOffsetFor(radioAddress + 1, memoryConfig, contents), (value >> 8) & 0xff);
420
474
  }
421
475
  else {
422
476
  const encoded = encodeRawValue(field.value, settingValue ?? (field.value?.kind === 'ascii' ? '' : 0), length);
@@ -434,12 +488,12 @@ function isStructInstanceEmpty(struct, contents, memoryConfig, instanceIndex) {
434
488
  if (!struct.emptyWhen) {
435
489
  return false;
436
490
  }
437
- const base = parseSeekAddress(struct.seek) + instanceIndex * (struct.stride ?? 0);
491
+ const base = structInstanceAddress(struct, instanceIndex);
438
492
  const firstByte = readByte(contents, bufferOffsetFor(base, memoryConfig, contents));
439
493
  return firstByte === struct.emptyWhen.equals;
440
494
  }
441
495
  function clearStructInstance(struct, contents, memoryConfig, instanceIndex) {
442
- const base = parseSeekAddress(struct.seek) + instanceIndex * (struct.stride ?? 0);
496
+ const base = structInstanceAddress(struct, instanceIndex);
443
497
  const length = struct.stride ?? 16;
444
498
  for (let index = 0; index < length; index += 1) {
445
499
  writeByte(contents, bufferOffsetFor(base + index, memoryConfig, contents), 0xff);