@djodjonx/x32-simulator 0.0.3 → 0.0.4

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.
Files changed (74) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/package.json +5 -1
  3. package/.commitlintrc.json +0 -3
  4. package/.github/workflows/publish.yml +0 -38
  5. package/.husky/commit-msg +0 -1
  6. package/.husky/pre-commit +0 -1
  7. package/.oxlintrc.json +0 -56
  8. package/INSTALL.md +0 -107
  9. package/docs/OSC-Communication.md +0 -184
  10. package/docs/X32-INTERNAL.md +0 -262
  11. package/docs/X32-OSC.pdf +0 -0
  12. package/docs/behringer-x32-x32-osc-remote-protocol-en-44463.pdf +0 -0
  13. package/src/application/use-cases/BroadcastUpdatesUseCase.ts +0 -120
  14. package/src/application/use-cases/ManageSessionsUseCase.ts +0 -9
  15. package/src/application/use-cases/ProcessPacketUseCase.ts +0 -26
  16. package/src/application/use-cases/SimulationService.ts +0 -146
  17. package/src/domain/entities/SubscriptionManager.ts +0 -126
  18. package/src/domain/entities/X32State.ts +0 -78
  19. package/src/domain/models/MeterConfig.ts +0 -22
  20. package/src/domain/models/MeterData.ts +0 -59
  21. package/src/domain/models/OscMessage.ts +0 -93
  22. package/src/domain/models/X32Address.ts +0 -72
  23. package/src/domain/models/X32Node.ts +0 -43
  24. package/src/domain/models/types.ts +0 -86
  25. package/src/domain/ports/ILogger.ts +0 -27
  26. package/src/domain/ports/INetworkGateway.ts +0 -8
  27. package/src/domain/ports/IStateRepository.ts +0 -16
  28. package/src/domain/services/MeterService.ts +0 -46
  29. package/src/domain/services/OscMessageHandler.ts +0 -88
  30. package/src/domain/services/SchemaFactory.ts +0 -308
  31. package/src/domain/services/SchemaRegistry.ts +0 -67
  32. package/src/domain/services/StaticResponseService.ts +0 -52
  33. package/src/domain/services/strategies/BatchStrategy.ts +0 -74
  34. package/src/domain/services/strategies/MeterStrategy.ts +0 -45
  35. package/src/domain/services/strategies/NodeDiscoveryStrategy.ts +0 -36
  36. package/src/domain/services/strategies/OscCommandStrategy.ts +0 -22
  37. package/src/domain/services/strategies/StateAccessStrategy.ts +0 -71
  38. package/src/domain/services/strategies/StaticResponseStrategy.ts +0 -42
  39. package/src/domain/services/strategies/SubscriptionStrategy.ts +0 -56
  40. package/src/infrastructure/mappers/OscCodec.ts +0 -54
  41. package/src/infrastructure/repositories/InMemoryStateRepository.ts +0 -37
  42. package/src/infrastructure/services/ConsoleLogger.ts +0 -177
  43. package/src/infrastructure/services/UdpNetworkGateway.ts +0 -100
  44. package/src/presentation/cli/server.ts +0 -194
  45. package/src/presentation/library/library.ts +0 -139
  46. package/tests/application/use-cases/BroadcastUpdatesUseCase.test.ts +0 -104
  47. package/tests/application/use-cases/ManageSessionsUseCase.test.ts +0 -12
  48. package/tests/application/use-cases/ProcessPacketUseCase.test.ts +0 -49
  49. package/tests/application/use-cases/SimulationService.test.ts +0 -77
  50. package/tests/domain/entities/SubscriptionManager.test.ts +0 -50
  51. package/tests/domain/entities/X32State.test.ts +0 -52
  52. package/tests/domain/models/MeterData.test.ts +0 -23
  53. package/tests/domain/models/OscMessage.test.ts +0 -38
  54. package/tests/domain/models/X32Address.test.ts +0 -30
  55. package/tests/domain/models/X32Node.test.ts +0 -30
  56. package/tests/domain/services/MeterService.test.ts +0 -27
  57. package/tests/domain/services/OscMessageHandler.test.ts +0 -51
  58. package/tests/domain/services/SchemaRegistry.test.ts +0 -47
  59. package/tests/domain/services/StaticResponseService.test.ts +0 -15
  60. package/tests/domain/services/strategies/BatchStrategy.test.ts +0 -41
  61. package/tests/domain/services/strategies/MeterStrategy.test.ts +0 -19
  62. package/tests/domain/services/strategies/NodeDiscoveryStrategy.test.ts +0 -22
  63. package/tests/domain/services/strategies/StateAccessStrategy.test.ts +0 -49
  64. package/tests/domain/services/strategies/StaticResponseStrategy.test.ts +0 -15
  65. package/tests/domain/services/strategies/SubscriptionStrategy.test.ts +0 -45
  66. package/tests/infrastructure/mappers/OscCodec.test.ts +0 -41
  67. package/tests/infrastructure/repositories/InMemoryStateRepository.test.ts +0 -29
  68. package/tests/infrastructure/services/ConsoleLogger.test.ts +0 -74
  69. package/tests/infrastructure/services/UdpNetworkGateway.test.ts +0 -61
  70. package/tests/presentation/cli/server.test.ts +0 -178
  71. package/tests/presentation/library/library.test.ts +0 -13
  72. package/tsconfig.json +0 -21
  73. package/tsdown.config.ts +0 -15
  74. package/vitest.config.ts +0 -9
@@ -1,262 +0,0 @@
1
- Voici le texte converti en format Markdown structuré et technique.
2
-
3
- # Rapport Technique Exhaustif : Conception et Implémentation d'un Simulateur de Console Numérique Behringer X32 via le Protocole Open Sound Control (OSC) en TypeScript
4
-
5
- ## 1. Introduction et Contexte Architectural
6
-
7
- La conception d'un simulateur haute fidélité pour la console de mixage numérique **Behringer X32** représente un défi d'ingénierie logicielle complexe, nécessitant une maîtrise approfondie des protocoles réseaux temps réel et de l'architecture des systèmes embarqués audio. Ce rapport technique a pour objectif de fournir une spécification exhaustive pour le développement d'un émulateur "serveur" en **TypeScript**, capable d'interagir de manière indiscernable avec les clients officiels tels que l'application **X32-Mix** (macOS/iPad) et des plugins tiers communiquant via OSC.
8
-
9
- Contrairement à une simple télécommande qui envoie des commandes, un simulateur doit répliquer le comportement d'état (**"State Machine"**) de la console physique. Il doit :
10
-
11
- * Gérer les connexions concurrentes.
12
- * Maintenir la cohérence des données entre les clients.
13
- * Répondre aux mécanismes de découverte ("Handshake").
14
- * **Point crucial :** Générer les flux de données de vumètres ("Metering") selon un format binaire propriétaire spécifique.
15
-
16
- L'analyse des documents techniques et des travaux de rétro-ingénierie révèle que le protocole X32 OSC s'écarte des spécifications standards sur des points critiques tels que l'endianness (boutisme) des données binaires et la gestion des souscriptions.
17
-
18
- ---
19
-
20
- ## 2. Fondamentaux du Protocole de Transport et Configuration Réseau
21
-
22
- ### 2.1 Architecture UDP et Gestion des Ports
23
-
24
- Le socle de communication de l'écosystème X32 repose quasi exclusivement sur le protocole **UDP (User Datagram Protocol)**. Pour un simulateur, cela implique que la couche réseau doit être résiliente à la perte de paquets et ne pas dépendre de mécanismes de reconnexion complexes propres à TCP.
25
-
26
- * **Port d'écoute :** **10023** (Standard pour X32/M32).
27
- * *Note :* La série X-Air utilise le port 10024.
28
-
29
-
30
- * **Implémentation Node.js :** Utilisation recommandée du module natif `dgram`.
31
- * **Binding :** Lier le socket à `0.0.0.0` pour accepter les diffusions ("broadcasts").
32
-
33
- ### 2.2 Structure des Paquets OSC et Alignement Mémoire
34
-
35
- Le protocole suit des règles d'alignement mémoire strictes (32 bits / 4 octets). Chaque composant (Address Pattern, Type Tag String, Arguments) doit être aligné.
36
-
37
- **Règle de Padding :** Si une chaîne (incluant son `\0` terminal) ne remplit pas un multiple de 4 octets, elle doit être complétée par des octets nuls.
38
-
39
- > **Exemple pour la commande `/info` :**
40
- > * Chaîne : `/info` (5 chars) + `\0` = 6 octets.
41
- > * Multiple de 4 supérieur = 8.
42
- > * Padding nécessaire = 2 octets nuls.
43
- > * Hexadécimal : `2f 69 6e 66 6f 00 00 00`
44
- >
45
- >
46
-
47
- Une erreur d'un seul octet rendra le paquet illisible pour X32-Mix.
48
-
49
- ### 2.3 Typage des Données et Robustesse du Serveur
50
-
51
- Le simulateur doit impérativement inclure les chaînes de types (Type Tags) dans les réponses.
52
-
53
- * **Int32 (`i`)** : Entier signé 32 bits, **Big-Endian**. (Énumérations, ID, Booléens).
54
- * **Float32 (`f`)** : Flottant IEEE 754 32 bits, **Big-Endian**. (Niveaux, Fréquences).
55
- * **String (`s`)** : Chaîne ASCII terminée par nul + Padding.
56
- * **Blob (`b`)** : "Binary Large Object". *Voir section 6.2 pour la spécificité d'endianness.*
57
-
58
- ---
59
-
60
- ## 3. Séquence d'Initialisation et Protocole de Découverte
61
-
62
- ### 3.1 Découverte du Serveur : La commande `/info`
63
-
64
- Dès le lancement, X32-Mix émet `/info`. Le simulateur doit répondre immédiatement avec 4 arguments de type string (`,ssss`).
65
-
66
- | Argument | Valeur Type | Description |
67
- | --- | --- | --- |
68
- | **Arg 1** | `V2.05` | Version du protocole serveur. |
69
- | **Arg 2** | `osc-server` | Nom réseau de la console. |
70
- | **Arg 3** | `X32` | **Critique.** Modèle de console. Utiliser `X32`, `M32`, `X32RACK`. Une valeur inconnue bloque le chargement de l'UI. |
71
- | **Arg 4** | `2.12` | Version du firmware (ex: `4.06`). |
72
-
73
- ### 3.2 Vérification d'État : La commande `/status`
74
-
75
- Réponse attendue (`,sss`) :
76
-
77
- 1. **État :** `active` (Moteur audio fonctionnel).
78
- 2. **IP :** Adresse IP locale du simulateur.
79
- 3. **Nom :** Nom de la console.
80
-
81
- ### 3.3 Négociation de la Session : Le mécanisme `/xremote`
82
-
83
- La X32 est un serveur passif par défaut. Le client doit s'abonner aux mises à jour.
84
-
85
- 1. **Réception :** Le simulateur reçoit `/xremote`.
86
- 2. **Enregistrement :** Ajout de l'IP/Port du client dans une "Subscribers List".
87
- 3. **Watchdog :** Timer de 10 secondes. Sans renouvellement, le client est éjecté.
88
- 4. **Feedback :** Tant que le client est abonné, **chaque** changement d'état interne (fader, mute) doit générer un message OSC sortant vers le client pour éviter l'effet "rubber-banding" (saut de fader).
89
-
90
- ---
91
-
92
- ## 4. Architecture de la Carte Mémoire (Address Map)
93
-
94
- ### 4.1 Hiérarchie Principale
95
-
96
- * `/ch/[01..32]` : Canaux d'entrée.
97
- * `/auxin/[01..08]` : Auxiliaires.
98
- * `/fxrtn/[01..08]` : Retours d'effets.
99
- * `/bus/[01..16]` : Bus de mixage.
100
- * `/mtx/[01..06]` : Matrices.
101
- * `/main/st`, `/main/m` : Sorties Master.
102
- * `/dca/[1..8]` : DCA.
103
- * `/config`, `/headamp` : Configuration et Préamplis.
104
-
105
- ### 4.2 Structure d'une Tranche ("Channel Strip")
106
-
107
- Sous-modules pour `/ch/01/...` :
108
-
109
- * `/config` : `name`, `icon`, `color`.
110
- * `/preamp` : `trim`, `inv` (phase), `hpf` (passe-haut).
111
- * `/gate` : `mode`, `thr`, `attack`, `hold`, `release`.
112
- * `/dyn` : Compresseur (`ratio`, `thr`, `knee`, etc.).
113
- * `/eq` : Paramétrique 4 bandes (`/eq/1` à `/eq/4`). Paramètres : `type`, `f`, `g`, `q`.
114
- * `/mix` :
115
- * `fader` : Float (0.0 - 1.0).
116
- * `on` : Mute (0/1). **Attention :** Logique inversée possible selon le contexte, mais généralement 1 = Audio Passant.
117
- * `pan` : 0.0 (G) - 1.0 (D).
118
-
119
-
120
-
121
- ### 4.3 Normalisation des Valeurs
122
-
123
- Les valeurs sont normalisées sur `[0.0, 1.0]`.
124
-
125
- * **Faders :** Non-linéaire. `0.0` = -∞ dB, `0.75` ≈ 0 dB, `1.0` = +10 dB.
126
- * **Fréquences :** Logarithmique (20 Hz - 20 kHz).
127
-
128
- ---
129
-
130
- ## 5. Gestion des Scènes et Fichiers
131
-
132
- Commandes clés à supporter :
133
-
134
- * `/-action/goscene ,i [index]` : Charger une scène.
135
- * `/-action/setscene ,i [index]` : Définir la scène courante.
136
-
137
- Le simulateur doit parser les fichiers `.scn` (texte clair contenant des commandes OSC) et les appliquer ligne par ligne à l'état interne.
138
-
139
- ---
140
-
141
- ## 6. Implémentation Avancée du Système de Vumètres (Metering)
142
-
143
- ### 6.1 La Commande `/meters`
144
-
145
- Requête client : `/meters ,si [Chemin] [Temps]`
146
-
147
- * `/meters/0` : Global.
148
- * `/meters/1` : Canaux détaillés (Entrées + Gate + Dyn).
149
- * Mise à jour continue (~50ms) pendant 10s si Temps = 0.
150
-
151
- ### 6.2 Le Format Binaire Hybride (Mixed Endianness)
152
-
153
- **Anomalie Critique :** Alors que l'en-tête OSC est en Big-Endian, le contenu du Blob ("Payload") contenant les vumètres utilise le **Little-Endian** pour les flottants.
154
-
155
- **Structure du Blob :**
156
-
157
- 1. **Taille du Blob (Int32)** : **Big-Endian** (Format OSC Standard).
158
- 2. **Taille Interne (Int32)** : **Big-Endian** (Début du Payload).
159
- 3. **Nombre d'Éléments (Int32)** : **Little-Endian**.
160
- 4. **Données (Float32 Array)** : **Little-Endian**.
161
-
162
- **Implémentation TypeScript :**
163
- L'utilisation de bibliothèques OSC standard échouera ici. Il faut construire le buffer manuellement.
164
-
165
- ```typescript
166
- // Exemple conceptuel de génération de blob pour /meters/1 (96 valeurs)
167
- function generateMeterBlob(values: number[]): Buffer {
168
- const numFloats = values.length; // 96
169
- const headerBytes = 4; // Taille interne
170
- const countBytes = 4; // Nombre d'éléments
171
- const dataBytes = numFloats * 4;
172
- const totalBlobSize = headerBytes + countBytes + dataBytes;
173
-
174
- // Création du buffer pour le contenu du blob
175
- const blobBuffer = Buffer.alloc(totalBlobSize);
176
- let offset = 0;
177
-
178
- // 1. Taille Interne (Big Endian) - Spécificité X32
179
- blobBuffer.writeInt32BE(totalBlobSize, offset);
180
- offset += 4;
181
-
182
- // 2. Compteur d'éléments (Little Endian)
183
- blobBuffer.writeInt32LE(numFloats, offset);
184
- offset += 4;
185
-
186
- // 3. Données Flottantes (Little Endian)
187
- for (let i = 0; i < numFloats; i++) {
188
- blobBuffer.writeFloatLE(values[i], offset);
189
- offset += 4;
190
- }
191
-
192
- return blobBuffer;
193
- }
194
-
195
- ```
196
-
197
- ### 6.3 Mapping des Vumètres (`/meters/1`)
198
-
199
- * **0-31 :** Canaux 1-32 (Pre-Fader).
200
- * **32-63 :** Gate Gain Reduction.
201
- * **64-95 :** Compressor Gain Reduction.
202
-
203
- ---
204
-
205
- ## 7. Architecture Logicielle du Simulateur en TypeScript
206
-
207
- ### 7.1 Gestionnaire d'État (State Store)
208
-
209
- Utiliser une structure arborescente émettant des événements.
210
-
211
- ```typescript
212
- type X32Parameter = number | string;
213
-
214
- class X32Node {
215
- private value: X32Parameter;
216
- private path: string;
217
-
218
- setValue(val: X32Parameter) {
219
- this.value = val;
220
- this.emit('change', this.path, this.value);
221
- }
222
- }
223
-
224
- ```
225
-
226
- ### 7.2 Boucle de Rétroaction
227
-
228
- Le serveur UDP s'abonne au Store :
229
- `StateStore.on('change', (path, value) => { Server.broadcast(path, value); });`
230
-
231
- ---
232
-
233
- ## 8. Méthodologie de Rétro-Ingénierie avec le Simulateur
234
-
235
- 1. **Mode Espion (Snooping) :** Logger chaque paquet entrant pour mapper les commandes inconnues envoyées par X32-Mix.
236
- 2. **Validation par Écho :** Envoyer périodiquement des commandes au client pour vérifier si l'UI réagit (boutons qui tournent).
237
- 3. **Analyse Wireshark :** Filtrer sur `udp.port == 10023` pour valider les séquences d'initialisation et l'endianness des blobs.
238
-
239
- ---
240
-
241
- ## 9. Conclusion et Tableaux Récapitulatifs
242
-
243
- La réussite du simulateur repose sur le respect strict des spécificités Behringer : **Port 10023**, **Handshake précis**, **Souscription active**, et **Endianness hybride** pour les vumètres.
244
-
245
- ### Tableau Récapitulatif des Types OSC X32
246
-
247
- | Type Tag | Description | Encodage Réseau (Standard) | Encodage Vumètres (Blob Payload) |
248
- | --- | --- | --- | --- |
249
- | `,i` | Entier 32 bits | Big-Endian | N/A |
250
- | `,f` | Flottant 32 bits | Big-Endian | **Little-Endian** |
251
- | `,s` | Chaîne | ASCII + Padding | N/A |
252
- | `,b` | Blob | Taille en Big-Endian | **Mixte** (Taille BE, Data LE) |
253
-
254
- ### Tableau des Principaux Chemins OSC
255
-
256
- | Chemin | Description | Arguments Typiques |
257
- | --- | --- | --- |
258
- | `/info` | Découverte serveur | Réponse: `,ssss` |
259
- | `/status` | État système | Réponse: `,sss` |
260
- | `/xremote` | Souscription | Aucun |
261
- | `/ch/[01-32]/mix/fader` | Fader Canal | `,f` [0.0 - 1.0] |
262
- | `/meters/[id]` | Requête Vumètres | `,b` (Blob réponse) |
package/docs/X32-OSC.pdf DELETED
Binary file
@@ -1,120 +0,0 @@
1
- import { SubscriptionManager } from '../../domain/entities/SubscriptionManager';
2
- import { INetworkGateway } from '../../domain/ports/INetworkGateway';
3
- import { X32State } from '../../domain/entities/X32State';
4
- import { SchemaRegistry } from '../../domain/services/SchemaRegistry';
5
- import { MeterService } from '../../domain/services/MeterService';
6
- import { ILogger, LogCategory } from '../../domain/ports/ILogger';
7
-
8
- export class BroadcastUpdatesUseCase {
9
- constructor(
10
- private subscriptionManager: SubscriptionManager,
11
- private state: X32State,
12
- private gateway: INetworkGateway,
13
- private logger: ILogger,
14
- private meterService: MeterService,
15
- private schemaRegistry: SchemaRegistry
16
- ) {}
17
-
18
- public execute(): void {
19
- this.subscriptionManager.getSubscribers().forEach(sub => {
20
- if (sub.type === 'path' || !sub.alias) {
21
- if (sub.type === 'meter' && sub.meterPath) {
22
- const meterData = this.meterService.generateMeterData(sub.meterPath, this.state);
23
- this.gateway.send({ address: sub.address, port: sub.port }, sub.meterPath, [meterData.toBlob()]);
24
- }
25
- return;
26
- }
27
-
28
- const rinfo = { address: sub.address, port: sub.port };
29
-
30
- if (sub.type === 'batch' && sub.paths) {
31
- if (sub.paths.length === 1 && sub.paths[0].startsWith('/meters')) {
32
- const meterData = this.meterService.generateMeterData(sub.paths[0], this.state);
33
- this.gateway.send(rinfo, sub.alias, [meterData.toBlob()]);
34
- return;
35
- }
36
-
37
- let stride: number;
38
- const dataSize = sub.paths.length * 4;
39
- const factor = sub.factor || 0;
40
- const count = sub.count || 0;
41
-
42
- if (factor > 0) {
43
- stride = Math.max(factor * 4, dataSize);
44
- } else {
45
- stride = (sub.paths.length === 3) ? 16 : dataSize;
46
- }
47
-
48
- const blobSize = count * stride;
49
- if (isNaN(blobSize) || blobSize < 0) {
50
- this.logger.error(LogCategory.SYSTEM, `Invalid blob size`, { alias: sub.alias, blobSize, count, stride, factor, dataSize });
51
- return;
52
- }
53
-
54
- const blob = Buffer.alloc(blobSize);
55
-
56
- for (let i = 0; i < count; i++) {
57
- const root = this.schemaRegistry.getRootFromIndex(sub.start! + i);
58
- let currentOffset = i * stride;
59
-
60
- if (!root) { continue; }
61
-
62
- sub.paths.forEach((s, pIdx) => {
63
- if (pIdx * 4 >= stride) return;
64
-
65
- if (s.startsWith('/meters')) {
66
- blob.writeFloatLE(0.0, currentOffset);
67
- } else {
68
- const target = `${root}${s}`;
69
- const node = this.schemaRegistry.getNode(target);
70
- let val = this.state.get(target);
71
- if (val === undefined) val = node ? node.default : 0;
72
-
73
- if (typeof val === 'number') {
74
- if (node && node.type === 'f') {
75
- blob.writeFloatLE(val, currentOffset);
76
- }
77
- else blob.writeInt32LE(val, currentOffset);
78
- } else blob.writeInt32LE(0, currentOffset);
79
- }
80
- currentOffset += 4;
81
- });
82
- }
83
- this.gateway.send(rinfo, sub.alias, [blob]);
84
- } else if (sub.type === 'format' && sub.pattern) {
85
- const stride = sub.factor ? sub.factor * 4 : 4;
86
- const start = sub.start || 0;
87
- const count = sub.count || 0;
88
-
89
- const totalSize = (start + count) * stride;
90
- if (isNaN(totalSize) || totalSize < 0) return;
91
-
92
- const blob = Buffer.alloc(totalSize);
93
- let offset = start * stride;
94
-
95
- for (let i = start; i < start + count; i++) {
96
- const id = i.toString().padStart(2, '0');
97
- const target = sub.pattern!.replace(/\*\*?/, id);
98
- let val = this.state.get(target);
99
- if (val === undefined) val = 0;
100
- if (typeof val === 'number') {
101
- const node = this.schemaRegistry.getNode(target);
102
- if (node && node.type === 'f') blob.writeFloatLE(val, offset);
103
- else blob.writeInt32LE(val, offset);
104
- } else blob.writeInt32LE(0, offset);
105
- offset += stride;
106
- }
107
- this.gateway.send(rinfo, sub.alias, [blob]);
108
- }
109
- });
110
- }
111
-
112
- public broadcastSingleChange(path: string, value: number | string): void {
113
- this.subscriptionManager.getSubscribers().forEach(sub => {
114
- const match = path === sub.path || sub.path === '/xremote' || (sub.path && sub.path.includes('*') && path.startsWith(sub.path.split('*')[0]));
115
- if (match) {
116
- this.gateway.send({ address: sub.address, port: sub.port }, path, [value]);
117
- }
118
- });
119
- }
120
- }
@@ -1,9 +0,0 @@
1
- import { SubscriptionManager } from '../../domain/entities/SubscriptionManager';
2
-
3
- export class ManageSessionsUseCase {
4
- constructor(private subscriptionManager: SubscriptionManager) {}
5
-
6
- public cleanup(): void {
7
- this.subscriptionManager.cleanup();
8
- }
9
- }
@@ -1,26 +0,0 @@
1
- import { OscPacket, OscArgument, RemoteClient } from '../../domain/models/types';
2
- import { OscMessageHandler } from '../../domain/services/OscMessageHandler';
3
- import { INetworkGateway } from '../../domain/ports/INetworkGateway';
4
-
5
- export class ProcessPacketUseCase {
6
- constructor(
7
- private messageHandler: OscMessageHandler,
8
- private gateway: INetworkGateway
9
- ) {}
10
-
11
- public execute(packet: OscPacket, rinfo: RemoteClient): void {
12
- if (packet.oscType === 'bundle') {
13
- packet.elements?.forEach((el: OscPacket) => this.execute(el, rinfo));
14
- } else if (packet.oscType === 'message') {
15
- const args = packet.args.map((arg: OscArgument) => {
16
- if (typeof arg === 'object' && arg !== null && 'value' in arg) return arg.value;
17
- return arg;
18
- });
19
-
20
- const replies = this.messageHandler.handle({ address: packet.address, args }, rinfo);
21
- replies.forEach(reply => {
22
- this.gateway.send(rinfo, reply.address, reply.args);
23
- });
24
- }
25
- }
26
- }
@@ -1,146 +0,0 @@
1
- import { ProcessPacketUseCase } from './ProcessPacketUseCase';
2
- import { BroadcastUpdatesUseCase } from './BroadcastUpdatesUseCase';
3
- import { ManageSessionsUseCase } from './ManageSessionsUseCase';
4
- import { INetworkGateway } from '../../domain/ports/INetworkGateway';
5
- import { ILogger, LogCategory } from '../../domain/ports/ILogger';
6
- import { IStateRepository } from '../../domain/ports/IStateRepository';
7
- import { SubscriptionManager } from '../../domain/entities/SubscriptionManager';
8
- import { OscMessageHandler } from '../../domain/services/OscMessageHandler';
9
- import { MeterService } from '../../domain/services/MeterService';
10
- import { SchemaRegistry } from '../../domain/services/SchemaRegistry';
11
- import { StaticResponseService } from '../../domain/services/StaticResponseService';
12
- import * as os from 'os';
13
-
14
- /**
15
- * Gets the first non-internal IPv4 address found on this machine.
16
- * @returns Detected IP or localhost.
17
- */
18
- function getLocalIp(): string {
19
- const interfaces = os.networkInterfaces();
20
- for (const name of Object.keys(interfaces)) {
21
- for (const iface of interfaces[name]!) {
22
- if (iface.family === 'IPv4' && !iface.internal) {
23
- return iface.address;
24
- }
25
- }
26
- }
27
- return '127.0.0.1';
28
- }
29
-
30
- /**
31
- * The core service that manages the X32 simulation, including networking,
32
- * state management, and OSC message handling.
33
- */
34
- export class SimulationService {
35
- private subscriptionManager: SubscriptionManager;
36
- private messageHandler: OscMessageHandler;
37
-
38
- // Use Cases
39
- private processPacket: ProcessPacketUseCase;
40
- private broadcastUpdates: BroadcastUpdatesUseCase;
41
- private manageSessions: ManageSessionsUseCase;
42
-
43
- // Services
44
- private meterService: MeterService;
45
- private staticResponseService: StaticResponseService;
46
-
47
- private updateInterval: NodeJS.Timeout | null = null;
48
- private cleanupInterval: NodeJS.Timeout | null = null;
49
-
50
- /**
51
- * Creates a new SimulationService instance.
52
- * @param gateway - The network gateway for OSC communication.
53
- * @param logger - The logger service.
54
- * @param stateRepo - The repository managing the mixer state.
55
- * @param schemaRegistry - The registry for X32 OSC schema.
56
- * @param port - UDP port to listen on (default 10023).
57
- * @param ip - IP address to bind to (default '0.0.0.0').
58
- * @param name - Reported console name.
59
- * @param model - Reported console model.
60
- */
61
- constructor(
62
- private gateway: INetworkGateway,
63
- private logger: ILogger,
64
- private stateRepo: IStateRepository,
65
- private schemaRegistry: SchemaRegistry,
66
- private port: number = 10023,
67
- private ip: string = '0.0.0.0',
68
- name: string = 'osc-server',
69
- model: string = 'X32'
70
- ) {
71
- // Initialize Domain Entities
72
- const state = this.stateRepo.getState();
73
- this.subscriptionManager = new SubscriptionManager(logger);
74
-
75
- // Initialize Services
76
- this.meterService = new MeterService();
77
- this.staticResponseService = new StaticResponseService();
78
-
79
- const reportedIp = (this.ip === '0.0.0.0') ? getLocalIp() : this.ip;
80
- this.messageHandler = new OscMessageHandler(
81
- state,
82
- this.subscriptionManager,
83
- logger,
84
- reportedIp,
85
- name,
86
- model,
87
- this.meterService,
88
- this.schemaRegistry,
89
- this.staticResponseService
90
- );
91
-
92
- // Initialize Use Cases
93
- this.processPacket = new ProcessPacketUseCase(this.messageHandler, gateway);
94
- this.broadcastUpdates = new BroadcastUpdatesUseCase(
95
- this.subscriptionManager,
96
- state,
97
- gateway,
98
- logger,
99
- this.meterService,
100
- this.schemaRegistry
101
- );
102
- this.manageSessions = new ManageSessionsUseCase(this.subscriptionManager);
103
-
104
- // Bind State Changes to Broadcast
105
- state.on('change', (evt: { address: string, value: number | string }) => {
106
- this.logger.info(LogCategory.STATE, `State Changed`, evt);
107
- this.broadcastUpdates.broadcastSingleChange(evt.address, evt.value);
108
- });
109
-
110
- // Bind Gateway Packet Handling
111
- this.gateway.onPacket((packet, source) => this.processPacket.execute(packet, source));
112
- }
113
-
114
- /**
115
- * Starts the simulator server and internal loops.
116
- */
117
- public async start(): Promise<void> {
118
- await this.gateway.start(this.port, this.ip);
119
-
120
- // Cleanup expired subscribers every 5s
121
- this.cleanupInterval = setInterval(() => {
122
- this.manageSessions.cleanup();
123
- }, 5000);
124
-
125
- // Send updates every 100ms
126
- this.updateInterval = setInterval(() => {
127
- this.broadcastUpdates.execute();
128
- }, 100);
129
- }
130
-
131
- /**
132
- * Stops the simulator server and stops all internal loops.
133
- */
134
- public async stop(): Promise<void> {
135
- if (this.updateInterval) clearInterval(this.updateInterval);
136
- if (this.cleanupInterval) clearInterval(this.cleanupInterval);
137
- await this.gateway.stop();
138
- }
139
-
140
- /**
141
- * Resets the mixer state to default values.
142
- */
143
- public resetState() {
144
- this.stateRepo.reset();
145
- }
146
- }