@springfield/ham-radio-utils 4.1.2 → 4.3.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.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  export * from './memory/segmented-memory.js';
2
+ export * from './memory/memory-map-codec.js';
3
+ export * from './memory/memory-map-channels.js';
4
+ export * from './memory/memory-map-ui.js';
2
5
  export * from './test-utils/radio-channel-factory.js';
3
6
  export * from './test-utils/radio-programmed-channel-factory.js';
4
7
  export * from './utils/band-plan.js';
@@ -11,4 +14,5 @@ export * from './utils/to-hex-words.js';
11
14
  export * from './utils/ui-logger.js';
12
15
  export * from './utils/ui-logger-factory.js';
13
16
  export { default as radioProtocolSchema } from './schemas/radio-protocol-schema.json';
17
+ export { default as radioMemoryMapSchema } from './schemas/radio-memory-map-schema.json';
14
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAE7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,kDAAkD,CAAC;AAEjE,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAG7C,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,sCAAsC,CAAuB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,2BAA2B,CAAC;AAE1C,cAAc,uCAAuC,CAAC;AACtD,cAAc,kDAAkD,CAAC;AAEjE,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAG7C,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,sCAAsC,CAAuB;AAC5G,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wCAAwC,CAAuB"}
package/dist/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  export * from './memory/segmented-memory.js';
2
+ export * from './memory/memory-map-codec.js';
3
+ export * from './memory/memory-map-channels.js';
4
+ export * from './memory/memory-map-ui.js';
2
5
  export * from './test-utils/radio-channel-factory.js';
3
6
  export * from './test-utils/radio-programmed-channel-factory.js';
4
7
  export * from './utils/band-plan.js';
@@ -12,4 +15,5 @@ export * from './utils/ui-logger.js';
12
15
  export * from './utils/ui-logger-factory.js';
13
16
  // Export the radio protocol schema
14
17
  export { default as radioProtocolSchema } from './schemas/radio-protocol-schema.json' with { type: 'json' };
18
+ export { default as radioMemoryMapSchema } from './schemas/radio-memory-map-schema.json' with { type: 'json' };
15
19
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAE7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,kDAAkD,CAAC;AAEjE,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAE7C,mCAAmC;AACnC,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,sCAAsC,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC","sourcesContent":["export * from './memory/segmented-memory.js';\n\nexport * from './test-utils/radio-channel-factory.js';\nexport * from './test-utils/radio-programmed-channel-factory.js';\n\nexport * from './utils/band-plan.js';\nexport * from './utils/frequency-display.js';\nexport * from './utils/memory-channel-utils.js';\nexport * from './utils/memory-data-utils.js';\nexport * from './utils/operator-class-mapper.js';\nexport * from './utils/schema-validator.js';\nexport * from './utils/to-hex-words.js';\nexport * from './utils/ui-logger.js';\nexport * from './utils/ui-logger-factory.js';\n\n// Export the radio protocol schema\nexport { default as radioProtocolSchema } from './schemas/radio-protocol-schema.json' with { type: 'json' };\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,2BAA2B,CAAC;AAE1C,cAAc,uCAAuC,CAAC;AACtD,cAAc,kDAAkD,CAAC;AAEjE,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAE7C,mCAAmC;AACnC,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,sCAAsC,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAC5G,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wCAAwC,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC","sourcesContent":["export * from './memory/segmented-memory.js';\nexport * from './memory/memory-map-codec.js';\nexport * from './memory/memory-map-channels.js';\nexport * from './memory/memory-map-ui.js';\n\nexport * from './test-utils/radio-channel-factory.js';\nexport * from './test-utils/radio-programmed-channel-factory.js';\n\nexport * from './utils/band-plan.js';\nexport * from './utils/frequency-display.js';\nexport * from './utils/memory-channel-utils.js';\nexport * from './utils/memory-data-utils.js';\nexport * from './utils/operator-class-mapper.js';\nexport * from './utils/schema-validator.js';\nexport * from './utils/to-hex-words.js';\nexport * from './utils/ui-logger.js';\nexport * from './utils/ui-logger-factory.js';\n\n// Export the radio protocol schema\nexport { default as radioProtocolSchema } from './schemas/radio-protocol-schema.json' with { type: 'json' };\nexport { default as radioMemoryMapSchema } from './schemas/radio-memory-map-schema.json' with { type: 'json' };\n"]}
@@ -0,0 +1,23 @@
1
+ import { type RadioMemoryConfig, type RadioMemoryMap, type RadioProgram, type RadioProgrammedChannel, type RadioSettings } from '@springfield/ham-radio-api';
2
+ /**
3
+ * Project decoded memory-map channel structs into RadioProgrammedChannel[].
4
+ */
5
+ export declare function bindingsToChannels(memoryMap: RadioMemoryMap, settings: RadioSettings): RadioProgrammedChannel[];
6
+ /**
7
+ * Strip channel-bound structs from decoded settings so RadioProgram.settings
8
+ * only contains radio-wide settings.
9
+ */
10
+ export declare function settingsWithoutChannels(memoryMap: RadioMemoryMap, settings: RadioSettings): RadioSettings;
11
+ /**
12
+ * Merge RadioProgram channels back into a memory-map settings bag for encode.
13
+ */
14
+ export declare function programToChannelSettings(memoryMap: RadioMemoryMap, program: RadioProgram): RadioSettings;
15
+ /**
16
+ * Decode a full RadioProgram (channels + radio-wide settings) from memory.
17
+ */
18
+ export declare function decodeRadioProgram(memoryMap: RadioMemoryMap, contents: Uint8Array, memoryConfig: RadioMemoryConfig): RadioProgram;
19
+ /**
20
+ * Encode a RadioProgram into an existing memory image (patch in place).
21
+ */
22
+ export declare function encodeRadioProgram(memoryMap: RadioMemoryMap, program: RadioProgram, contents: Uint8Array, memoryConfig: RadioMemoryConfig): Uint8Array;
23
+ //# sourceMappingURL=memory-map-channels.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,188 @@
1
+ import { Frequency, RadioToneType, } from '@springfield/ham-radio-api';
2
+ import { decodeMemoryMap, encodeMemoryMap } from './memory-map-codec.js';
3
+ function isToneValue(value) {
4
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value) && 'mode' in value);
5
+ }
6
+ function toneToRadioTone(value) {
7
+ if (!isToneValue(value) || value.mode === 'none') {
8
+ return { tone: 0, type: RadioToneType.CTCSS };
9
+ }
10
+ if (value.mode === 'ctcss') {
11
+ return { tone: value.value, type: RadioToneType.CTCSS };
12
+ }
13
+ return { tone: value.code, type: RadioToneType.DCS };
14
+ }
15
+ function radioToneToMapTone(tone) {
16
+ if (!tone || !tone.tone) {
17
+ return { mode: 'none' };
18
+ }
19
+ if (tone.type === RadioToneType.DCS) {
20
+ return { mode: 'dcs', code: Number(tone.tone), polarity: 'N' };
21
+ }
22
+ return { mode: 'ctcss', value: Number(tone.tone) };
23
+ }
24
+ function asRecord(value) {
25
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
26
+ return undefined;
27
+ }
28
+ return value;
29
+ }
30
+ /**
31
+ * Project decoded memory-map channel structs into RadioProgrammedChannel[].
32
+ */
33
+ export function bindingsToChannels(memoryMap, settings) {
34
+ const bindings = memoryMap.channelBindings;
35
+ if (!bindings) {
36
+ return [];
37
+ }
38
+ const records = settings[bindings.records];
39
+ const names = bindings.names ? settings[bindings.names] : undefined;
40
+ const recordItems = Array.isArray(records) ? records : [];
41
+ const nameItems = Array.isArray(names) ? names : [];
42
+ const nameField = bindings.nameField ?? 'name';
43
+ const boundFieldIds = new Set([
44
+ bindings.receiveFrequency,
45
+ bindings.transmitFrequency,
46
+ bindings.receiveTone,
47
+ bindings.transmitTone,
48
+ ]);
49
+ const channels = [];
50
+ for (let index = 0; index < recordItems.length; index += 1) {
51
+ const record = asRecord(recordItems[index]);
52
+ if (!record) {
53
+ continue;
54
+ }
55
+ const nameRecord = asRecord(nameItems[index]);
56
+ const nameValue = nameRecord?.[nameField];
57
+ const name = typeof nameValue === 'string' ? nameValue : '';
58
+ const receiveFrequency = Number(record[bindings.receiveFrequency] ?? 0);
59
+ const transmitFrequency = Number(record[bindings.transmitFrequency] ?? 0);
60
+ const channelSettings = {};
61
+ for (const [fieldId, fieldValue] of Object.entries(record)) {
62
+ if (boundFieldIds.has(fieldId) || fieldId.startsWith('_')) {
63
+ continue;
64
+ }
65
+ channelSettings[fieldId] = fieldValue;
66
+ }
67
+ // Map Chirp lowpower index onto legacy transmitPower watts for UV-5R UI compatibility.
68
+ if (typeof channelSettings.lowpower === 'number') {
69
+ channelSettings.transmitPower = channelSettings.lowpower === 0 ? 5 : 1;
70
+ }
71
+ if (typeof channelSettings.wide === 'boolean') {
72
+ channelSettings.mode = channelSettings.wide ? 'FM' : 'NFM';
73
+ }
74
+ if (typeof channelSettings.scan === 'boolean') {
75
+ channelSettings.skip = channelSettings.scan ? '' : 'S';
76
+ }
77
+ const radioChannel = {
78
+ name,
79
+ receiveFrequency: Frequency(receiveFrequency),
80
+ transmitFrequency: Frequency(transmitFrequency),
81
+ receiveTone: toneToRadioTone(record[bindings.receiveTone]),
82
+ transmitTone: toneToRadioTone(record[bindings.transmitTone]),
83
+ };
84
+ channels.push({
85
+ channelNumber: index,
86
+ radioChannel,
87
+ settings: Object.keys(channelSettings).length > 0 ? channelSettings : undefined,
88
+ });
89
+ }
90
+ return channels;
91
+ }
92
+ /**
93
+ * Strip channel-bound structs from decoded settings so RadioProgram.settings
94
+ * only contains radio-wide settings.
95
+ */
96
+ export function settingsWithoutChannels(memoryMap, settings) {
97
+ const bindings = memoryMap.channelBindings;
98
+ if (!bindings) {
99
+ return settings;
100
+ }
101
+ const next = { ...settings };
102
+ delete next[bindings.records];
103
+ if (bindings.names) {
104
+ delete next[bindings.names];
105
+ }
106
+ return next;
107
+ }
108
+ /**
109
+ * Merge RadioProgram channels back into a memory-map settings bag for encode.
110
+ */
111
+ export function programToChannelSettings(memoryMap, program) {
112
+ const bindings = memoryMap.channelBindings;
113
+ if (!bindings) {
114
+ return program.settings;
115
+ }
116
+ const recordsStruct = memoryMap.structs.find((struct) => struct.id === bindings.records);
117
+ const namesStruct = bindings.names ? memoryMap.structs.find((struct) => struct.id === bindings.names) : undefined;
118
+ const count = recordsStruct?.count ?? 0;
119
+ const nameField = bindings.nameField ?? 'name';
120
+ const records = Array.from({ length: count }, () => null);
121
+ const names = Array.from({ length: count }, () => null);
122
+ for (const programmed of program.channels) {
123
+ const index = programmed.channelNumber;
124
+ if (index < 0 || index >= count) {
125
+ continue;
126
+ }
127
+ if (typeof programmed.radioChannel === 'string') {
128
+ continue;
129
+ }
130
+ const channel = programmed.radioChannel;
131
+ const channelSettings = programmed.settings ?? {};
132
+ const record = {
133
+ [bindings.receiveFrequency]: channel.receiveFrequency,
134
+ [bindings.transmitFrequency]: channel.transmitFrequency,
135
+ [bindings.receiveTone]: radioToneToMapTone(channel.receiveTone),
136
+ [bindings.transmitTone]: radioToneToMapTone(channel.transmitTone),
137
+ };
138
+ for (const [key, value] of Object.entries(channelSettings)) {
139
+ if (key === 'transmitPower') {
140
+ record.lowpower = value === 5 || value === 4 || value === 0 ? 0 : 1;
141
+ continue;
142
+ }
143
+ if (key === 'mode') {
144
+ record.wide = value === 'FM';
145
+ continue;
146
+ }
147
+ if (key === 'skip') {
148
+ record.scan = value !== 'S';
149
+ continue;
150
+ }
151
+ record[key] = value;
152
+ }
153
+ if (record.lowpower === undefined) {
154
+ record.lowpower = 0;
155
+ }
156
+ if (record.wide === undefined) {
157
+ record.wide = true;
158
+ }
159
+ if (record.scan === undefined) {
160
+ record.scan = true;
161
+ }
162
+ records[index] = record;
163
+ names[index] = { [nameField]: channel.name ?? '' };
164
+ }
165
+ const bag = { ...program.settings, [bindings.records]: records };
166
+ if (bindings.names && namesStruct) {
167
+ bag[bindings.names] = names;
168
+ }
169
+ return bag;
170
+ }
171
+ /**
172
+ * Decode a full RadioProgram (channels + radio-wide settings) from memory.
173
+ */
174
+ export function decodeRadioProgram(memoryMap, contents, memoryConfig) {
175
+ const decoded = decodeMemoryMap(memoryMap, contents, memoryConfig);
176
+ return {
177
+ channels: bindingsToChannels(memoryMap, decoded),
178
+ settings: settingsWithoutChannels(memoryMap, decoded),
179
+ };
180
+ }
181
+ /**
182
+ * Encode a RadioProgram into an existing memory image (patch in place).
183
+ */
184
+ export function encodeRadioProgram(memoryMap, program, contents, memoryConfig) {
185
+ const bag = programToChannelSettings(memoryMap, program);
186
+ return encodeMemoryMap(memoryMap, bag, contents, memoryConfig);
187
+ }
188
+ //# sourceMappingURL=memory-map-channels.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,22 @@
1
+ import type { RadioMemoryConfig, RadioMemoryMap, RadioSettings } from '@springfield/ham-radio-api';
2
+ /**
3
+ * Parse a seek address from a number or hex/decimal string.
4
+ */
5
+ export declare function parseSeekAddress(seek: number | string): number;
6
+ /**
7
+ * Map a radio EEPROM address to an offset in the memory buffer.
8
+ *
9
+ * Packed buffers (driver read) concatenate segments in config order.
10
+ * Sparse buffers that cover the highest segment end address use absolute offsets.
11
+ */
12
+ export declare function radioAddressToBufferOffset(radioAddress: number, memoryConfig: RadioMemoryConfig, bufferLength: number): number;
13
+ /**
14
+ * Decode radio-wide settings from a memory image using a JSON memory map.
15
+ */
16
+ export declare function decodeMemoryMap(memoryMap: RadioMemoryMap, contents: Uint8Array, memoryConfig: RadioMemoryConfig): RadioSettings;
17
+ /**
18
+ * Encode radio-wide settings into an existing memory image in place.
19
+ * Returns the same buffer for convenience.
20
+ */
21
+ export declare function encodeMemoryMap(memoryMap: RadioMemoryMap, settings: RadioSettings, contents: Uint8Array, memoryConfig: RadioMemoryConfig): Uint8Array;
22
+ //# sourceMappingURL=memory-map-codec.d.ts.map
@@ -0,0 +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"}