@muhammedaksam/opentui-doom 0.2.0 → 0.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/doom/s_sound.c ADDED
@@ -0,0 +1,566 @@
1
+ //
2
+ // Copyright(C) 1993-1996 Id Software, Inc.
3
+ // Copyright(C) 2005-2014 Simon Howard
4
+ //
5
+ // This program is free software; you can redistribute it and/or
6
+ // modify it under the terms of the GNU General Public License
7
+ // as published by the Free Software Foundation; either version 2
8
+ // of the License, or (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU General Public License for more details.
14
+ //
15
+ // DESCRIPTION: none
16
+ //
17
+
18
+ #include <stdio.h>
19
+ #include <stdlib.h>
20
+
21
+ #include "i_sound.h"
22
+ #include "i_system.h"
23
+
24
+ #include "deh_str.h"
25
+ #include "doomfeatures.h"
26
+
27
+ #include "doomstat.h"
28
+ #include "doomtype.h"
29
+
30
+ #include "s_sound.h"
31
+ #include "sounds.h"
32
+
33
+ #include "m_argv.h"
34
+ #include "m_misc.h"
35
+ #include "m_random.h"
36
+
37
+ #include "p_local.h"
38
+ #include "w_wad.h"
39
+ #include "z_zone.h"
40
+
41
+ // when to clip out sounds
42
+ // Does not fit the large outdoor areas.
43
+
44
+ #define S_CLIPPING_DIST (1200 * FRACUNIT)
45
+
46
+ // Distance tp origin when sounds should be maxed out.
47
+ // This should relate to movement clipping resolution
48
+ // (see BLOCKMAP handling).
49
+ // In the source code release: (160*FRACUNIT). Changed back to the
50
+ // Vanilla value of 200 (why was this changed?)
51
+
52
+ #define S_CLOSE_DIST (200 * FRACUNIT)
53
+
54
+ // The range over which sound attenuates
55
+
56
+ #define S_ATTENUATOR ((S_CLIPPING_DIST - S_CLOSE_DIST) >> FRACBITS)
57
+
58
+ // Stereo separation
59
+
60
+ #define S_STEREO_SWING (96 * FRACUNIT)
61
+
62
+ #define NORM_PITCH 128
63
+ #define NORM_PRIORITY 64
64
+ #define NORM_SEP 128
65
+
66
+ typedef struct {
67
+ // sound information (if null, channel avail.)
68
+ sfxinfo_t *sfxinfo;
69
+
70
+ // origin of sound
71
+ mobj_t *origin;
72
+
73
+ // handle of the sound being played
74
+ int handle;
75
+
76
+ } channel_t;
77
+
78
+ // The set of channels available
79
+
80
+ static channel_t *channels;
81
+
82
+ // Maximum volume of a sound effect.
83
+ // Internal default is max out of 0-15.
84
+
85
+ int sfxVolume = 8;
86
+
87
+ // Maximum volume of music.
88
+
89
+ int musicVolume = 8;
90
+
91
+ // Internal volume level, ranging from 0-127
92
+
93
+ static int snd_SfxVolume;
94
+
95
+ // Whether songs are mus_paused
96
+
97
+ static boolean mus_paused;
98
+
99
+ // Music currently being played
100
+
101
+ static musicinfo_t *mus_playing = NULL;
102
+
103
+ // Number of channels to use
104
+
105
+ int snd_channels = 8;
106
+
107
+ //
108
+ // Initializes sound stuff, including volume
109
+ // Sets channels, SFX and music volume,
110
+ // allocates channel buffer, sets S_sfx lookup.
111
+ //
112
+
113
+ void S_Init(int sfxVolume, int musicVolume) {
114
+ int i;
115
+
116
+ I_PrecacheSounds(S_sfx, NUMSFX);
117
+
118
+ S_SetSfxVolume(sfxVolume);
119
+ S_SetMusicVolume(musicVolume);
120
+
121
+ // Allocating the internal channels for mixing
122
+ // (the maximum numer of sounds rendered
123
+ // simultaneously) within zone memory.
124
+ channels = Z_Malloc(snd_channels * sizeof(channel_t), PU_STATIC, 0);
125
+
126
+ // Free all channels for use
127
+ for (i = 0; i < snd_channels; i++) {
128
+ channels[i].sfxinfo = 0;
129
+ }
130
+
131
+ // no sounds are playing, and they are not mus_paused
132
+ mus_paused = 0;
133
+
134
+ // Note that sounds have not been cached (yet).
135
+ for (i = 1; i < NUMSFX; i++) {
136
+ S_sfx[i].lumpnum = S_sfx[i].usefulness = -1;
137
+ }
138
+
139
+ I_AtExit(S_Shutdown, true);
140
+ }
141
+
142
+ void S_Shutdown(void) {
143
+ I_ShutdownSound();
144
+ I_ShutdownMusic();
145
+ }
146
+
147
+ static void S_StopChannel(int cnum) {
148
+ int i;
149
+ channel_t *c;
150
+
151
+ c = &channels[cnum];
152
+
153
+ if (c->sfxinfo) {
154
+ // stop the sound playing
155
+
156
+ if (I_SoundIsPlaying(c->handle)) {
157
+ I_StopSound(c->handle);
158
+ }
159
+
160
+ // check to see if other channels are playing the sound
161
+
162
+ for (i = 0; i < snd_channels; i++) {
163
+ if (cnum != i && c->sfxinfo == channels[i].sfxinfo) {
164
+ break;
165
+ }
166
+ }
167
+
168
+ // degrade usefulness of sound data
169
+
170
+ c->sfxinfo->usefulness--;
171
+ c->sfxinfo = NULL;
172
+ }
173
+ }
174
+
175
+ //
176
+ // Per level startup code.
177
+ // Kills playing sounds at start of level,
178
+ // determines music if any, changes music.
179
+ //
180
+
181
+ void S_Start(void) {
182
+ int cnum;
183
+ int mnum;
184
+
185
+ // kill all playing sounds at start of level
186
+ // (trust me - a good idea)
187
+ for (cnum = 0; cnum < snd_channels; cnum++) {
188
+ if (channels[cnum].sfxinfo) {
189
+ S_StopChannel(cnum);
190
+ }
191
+ }
192
+
193
+ // start new music for the level
194
+ mus_paused = 0;
195
+
196
+ if (gamemode == commercial) {
197
+ mnum = mus_runnin + gamemap - 1;
198
+ } else {
199
+ int spmus[] = {
200
+ // Song - Who? - Where?
201
+
202
+ mus_e3m4, // American e4m1
203
+ mus_e3m2, // Romero e4m2
204
+ mus_e3m3, // Shawn e4m3
205
+ mus_e1m5, // American e4m4
206
+ mus_e2m7, // Tim e4m5
207
+ mus_e2m4, // Romero e4m6
208
+ mus_e2m6, // J.Anderson e4m7 CHIRON.WAD
209
+ mus_e2m5, // Shawn e4m8
210
+ mus_e1m9, // Tim e4m9
211
+ };
212
+
213
+ if (gameepisode < 4) {
214
+ mnum = mus_e1m1 + (gameepisode - 1) * 9 + gamemap - 1;
215
+ } else {
216
+ mnum = spmus[gamemap - 1];
217
+ }
218
+ }
219
+
220
+ S_ChangeMusic(mnum, true);
221
+ }
222
+
223
+ void S_StopSound(mobj_t *origin) {
224
+ int cnum;
225
+
226
+ for (cnum = 0; cnum < snd_channels; cnum++) {
227
+ if (channels[cnum].sfxinfo && channels[cnum].origin == origin) {
228
+ S_StopChannel(cnum);
229
+ break;
230
+ }
231
+ }
232
+ }
233
+
234
+ //
235
+ // S_GetChannel :
236
+ // If none available, return -1. Otherwise channel #.
237
+ //
238
+
239
+ static int S_GetChannel(mobj_t *origin, sfxinfo_t *sfxinfo) {
240
+ // channel number to use
241
+ int cnum;
242
+
243
+ channel_t *c;
244
+
245
+ // Find an open channel
246
+ for (cnum = 0; cnum < snd_channels; cnum++) {
247
+ if (!channels[cnum].sfxinfo) {
248
+ break;
249
+ } else if (origin && channels[cnum].origin == origin) {
250
+ S_StopChannel(cnum);
251
+ break;
252
+ }
253
+ }
254
+
255
+ // None available
256
+ if (cnum == snd_channels) {
257
+ // Look for lower priority
258
+ for (cnum = 0; cnum < snd_channels; cnum++) {
259
+ if (channels[cnum].sfxinfo->priority >= sfxinfo->priority) {
260
+ break;
261
+ }
262
+ }
263
+
264
+ if (cnum == snd_channels) {
265
+ // FUCK! No lower priority. Sorry, Charlie.
266
+ return -1;
267
+ } else {
268
+ // Otherwise, kick out lower priority.
269
+ S_StopChannel(cnum);
270
+ }
271
+ }
272
+
273
+ c = &channels[cnum];
274
+
275
+ // channel is decided to be cnum.
276
+ c->sfxinfo = sfxinfo;
277
+ c->origin = origin;
278
+
279
+ return cnum;
280
+ }
281
+
282
+ //
283
+ // Changes volume and stereo-separation variables
284
+ // from the norm of a sound effect to be played.
285
+ // If the sound is not audible, returns a 0.
286
+ // Otherwise, modifies parameters and returns 1.
287
+ //
288
+
289
+ static int S_AdjustSoundParams(mobj_t *listener, mobj_t *source, int *vol,
290
+ int *sep) {
291
+ fixed_t approx_dist;
292
+ fixed_t adx;
293
+ fixed_t ady;
294
+ angle_t angle;
295
+
296
+ // calculate the distance to sound origin
297
+ // and clip it if necessary
298
+ adx = abs(listener->x - source->x);
299
+ ady = abs(listener->y - source->y);
300
+
301
+ // From _GG1_ p.428. Appox. eucledian distance fast.
302
+ approx_dist = adx + ady - ((adx < ady ? adx : ady) >> 1);
303
+
304
+ if (gamemap != 8 && approx_dist > S_CLIPPING_DIST) {
305
+ return 0;
306
+ }
307
+
308
+ // angle of source to listener
309
+ angle = R_PointToAngle2(listener->x, listener->y, source->x, source->y);
310
+
311
+ if (angle > listener->angle) {
312
+ angle = angle - listener->angle;
313
+ } else {
314
+ angle = angle + (0xffffffff - listener->angle);
315
+ }
316
+
317
+ angle >>= ANGLETOFINESHIFT;
318
+
319
+ // stereo separation
320
+ *sep = 128 - (FixedMul(S_STEREO_SWING, finesine[angle]) >> FRACBITS);
321
+
322
+ // volume calculation
323
+ if (approx_dist < S_CLOSE_DIST) {
324
+ *vol = snd_SfxVolume;
325
+ } else if (gamemap == 8) {
326
+ if (approx_dist > S_CLIPPING_DIST) {
327
+ approx_dist = S_CLIPPING_DIST;
328
+ }
329
+
330
+ *vol = 15 + ((snd_SfxVolume - 15) *
331
+ ((S_CLIPPING_DIST - approx_dist) >> FRACBITS)) /
332
+ S_ATTENUATOR;
333
+ } else {
334
+ // distance effect
335
+ *vol = (snd_SfxVolume * ((S_CLIPPING_DIST - approx_dist) >> FRACBITS)) /
336
+ S_ATTENUATOR;
337
+ }
338
+
339
+ return (*vol > 0);
340
+ }
341
+
342
+ void S_StartSound(void *origin_p, int sfx_id) {
343
+ sfxinfo_t *sfx;
344
+ mobj_t *origin;
345
+ int rc;
346
+ int sep;
347
+ int cnum;
348
+ int volume;
349
+
350
+ origin = (mobj_t *)origin_p;
351
+ volume = snd_SfxVolume;
352
+
353
+ // check for bogus sound #
354
+ if (sfx_id < 1 || sfx_id > NUMSFX) {
355
+ I_Error("Bad sfx #: %d", sfx_id);
356
+ }
357
+
358
+ sfx = &S_sfx[sfx_id];
359
+
360
+ // Initialize sound parameters
361
+ if (sfx->link) {
362
+ volume += sfx->volume;
363
+
364
+ if (volume < 1) {
365
+ return;
366
+ }
367
+
368
+ if (volume > snd_SfxVolume) {
369
+ volume = snd_SfxVolume;
370
+ }
371
+ }
372
+
373
+ // Check to see if it is audible,
374
+ // and if not, modify the params
375
+ if (origin && origin != players[consoleplayer].mo) {
376
+ rc = S_AdjustSoundParams(players[consoleplayer].mo, origin, &volume, &sep);
377
+
378
+ if (origin->x == players[consoleplayer].mo->x &&
379
+ origin->y == players[consoleplayer].mo->y) {
380
+ sep = NORM_SEP;
381
+ }
382
+
383
+ if (!rc) {
384
+ return;
385
+ }
386
+ } else {
387
+ sep = NORM_SEP;
388
+ }
389
+
390
+ // kill old sound
391
+ S_StopSound(origin);
392
+
393
+ // try to find a channel
394
+ cnum = S_GetChannel(origin, sfx);
395
+
396
+ if (cnum < 0) {
397
+ return;
398
+ }
399
+
400
+ // increase the usefulness
401
+ if (sfx->usefulness++ < 0) {
402
+ sfx->usefulness = 1;
403
+ }
404
+
405
+ if (sfx->lumpnum < 0) {
406
+ sfx->lumpnum = I_GetSfxLumpNum(sfx);
407
+ }
408
+
409
+ channels[cnum].handle = I_StartSound(sfx, cnum, volume, sep);
410
+ }
411
+
412
+ //
413
+ // Stop and resume music, during game PAUSE.
414
+ //
415
+
416
+ void S_PauseSound(void) {
417
+ if (mus_playing && !mus_paused) {
418
+ I_PauseSong();
419
+ mus_paused = true;
420
+ }
421
+ }
422
+
423
+ void S_ResumeSound(void) {
424
+ if (mus_playing && mus_paused) {
425
+ I_ResumeSong();
426
+ mus_paused = false;
427
+ }
428
+ }
429
+
430
+ //
431
+ // Updates music & sounds
432
+ //
433
+
434
+ void S_UpdateSounds(mobj_t *listener) {
435
+ int audible;
436
+ int cnum;
437
+ int volume;
438
+ int sep;
439
+ sfxinfo_t *sfx;
440
+ channel_t *c;
441
+
442
+ I_UpdateSound();
443
+
444
+ for (cnum = 0; cnum < snd_channels; cnum++) {
445
+ c = &channels[cnum];
446
+ sfx = c->sfxinfo;
447
+
448
+ if (c->sfxinfo) {
449
+ if (I_SoundIsPlaying(c->handle)) {
450
+ // initialize parameters
451
+ volume = snd_SfxVolume;
452
+ sep = NORM_SEP;
453
+
454
+ if (sfx->link) {
455
+ volume += sfx->volume;
456
+ if (volume < 1) {
457
+ S_StopChannel(cnum);
458
+ continue;
459
+ } else if (volume > snd_SfxVolume) {
460
+ volume = snd_SfxVolume;
461
+ }
462
+ }
463
+
464
+ // check non-local sounds for distance clipping
465
+ // or modify their params
466
+ if (c->origin && listener != c->origin) {
467
+ audible = S_AdjustSoundParams(listener, c->origin, &volume, &sep);
468
+
469
+ if (!audible) {
470
+ S_StopChannel(cnum);
471
+ } else {
472
+ I_UpdateSoundParams(c->handle, volume, sep);
473
+ }
474
+ }
475
+ } else {
476
+ // if channel is allocated but sound has stopped,
477
+ // free it
478
+ S_StopChannel(cnum);
479
+ }
480
+ }
481
+ }
482
+ }
483
+
484
+ void S_SetMusicVolume(int volume) {
485
+ if (volume < 0 || volume > 127) {
486
+ I_Error("Attempt to set music volume at %d", volume);
487
+ }
488
+
489
+ I_SetMusicVolume(volume);
490
+ }
491
+
492
+ void S_SetSfxVolume(int volume) {
493
+ if (volume < 0 || volume > 127) {
494
+ I_Error("Attempt to set sfx volume at %d", volume);
495
+ }
496
+
497
+ snd_SfxVolume = volume;
498
+ }
499
+
500
+ //
501
+ // Starts some music with the music id found in sounds.h.
502
+ //
503
+
504
+ void S_StartMusic(int m_id) { S_ChangeMusic(m_id, false); }
505
+
506
+ void S_ChangeMusic(int musicnum, int looping) {
507
+ musicinfo_t *music = NULL;
508
+ char namebuf[9];
509
+ void *handle;
510
+
511
+ // The Doom IWAD file has two versions of the intro music: d_intro
512
+ // and d_introa. The latter is used for OPL playback.
513
+
514
+ if (musicnum == mus_intro &&
515
+ (snd_musicdevice == SNDDEVICE_ADLIB || snd_musicdevice == SNDDEVICE_SB)) {
516
+ musicnum = mus_introa;
517
+ }
518
+
519
+ if (musicnum <= mus_None || musicnum >= NUMMUSIC) {
520
+ I_Error("Bad music number %d", musicnum);
521
+ } else {
522
+ music = &S_music[musicnum];
523
+ }
524
+
525
+ if (mus_playing == music) {
526
+ return;
527
+ }
528
+
529
+ // shutdown old music
530
+ S_StopMusic();
531
+
532
+ // get lumpnum if neccessary
533
+ if (!music->lumpnum) {
534
+ M_snprintf(namebuf, sizeof(namebuf), "d_%s", DEH_String(music->name));
535
+ music->lumpnum = W_GetNumForName(namebuf);
536
+ } else {
537
+ // Rebuild namebuf if we already have lumpnum
538
+ M_snprintf(namebuf, sizeof(namebuf), "d_%s", DEH_String(music->name));
539
+ }
540
+
541
+ // For OpenTUI: Pass the music lump NAME (e.g., "d_e1m1") instead of binary
542
+ // data This allows our JavaScript bridge to find the corresponding MP3 file
543
+ // Still need to cache the lump so S_StopMusic can release it properly
544
+ music->data = W_CacheLumpNum(music->lumpnum, PU_STATIC);
545
+ handle = I_RegisterSong(namebuf, strlen(namebuf));
546
+ music->handle = handle;
547
+ I_PlaySong(handle, looping);
548
+
549
+ mus_playing = music;
550
+ }
551
+
552
+ boolean S_MusicPlaying(void) { return I_MusicIsPlaying(); }
553
+
554
+ void S_StopMusic(void) {
555
+ if (mus_playing) {
556
+ if (mus_paused) {
557
+ I_ResumeSong();
558
+ }
559
+
560
+ I_StopSong();
561
+ I_UnRegisterSong(mus_playing->handle);
562
+ W_ReleaseLumpNum(mus_playing->lumpnum);
563
+ mus_playing->data = NULL;
564
+ mus_playing = NULL;
565
+ }
566
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muhammedaksam/opentui-doom",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Play DOOM in your terminal using OpenTUI's framebuffer rendering and doomgeneric WASM",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -13,6 +13,8 @@
13
13
  "doom/doomgeneric_opentui.c",
14
14
  "doom/doom_js_sound_bridge.c",
15
15
  "doom/i_sound.c",
16
+ "doom/i_system.c",
17
+ "doom/s_sound.c",
16
18
  "sound",
17
19
  "scripts"
18
20
  ],
@@ -42,7 +44,10 @@
42
44
  ],
43
45
  "scripts": {
44
46
  "dev": "bun run --watch src/index.ts",
47
+ "dev:debug": "DOOM_DEBUG=1 bun run --watch src/index.ts",
45
48
  "build:doom": "bash ./scripts/build-doom.sh",
49
+ "build": "bun build src/index.ts --outdir dist --target node",
50
+ "typecheck": "bun x tsc --noEmit",
46
51
  "start": "bun run src/index.ts",
47
52
  "prepublishOnly": "bun run build:doom"
48
53
  },
@@ -34,10 +34,12 @@ fi
34
34
  # Create build directory
35
35
  mkdir -p "$BUILD_DIR"
36
36
 
37
- # Copy our platform file, sound bridge, and custom i_sound.c
37
+ # Copy our platform file, sound bridge, and custom files
38
38
  cp "$DOOM_DIR/doomgeneric_opentui.c" "$DOOM_DIR/doomgeneric/doomgeneric/"
39
39
  cp "$DOOM_DIR/doom_js_sound_bridge.c" "$DOOM_DIR/doomgeneric/doomgeneric/"
40
40
  cp "$DOOM_DIR/i_sound.c" "$DOOM_DIR/doomgeneric/doomgeneric/"
41
+ cp "$DOOM_DIR/s_sound.c" "$DOOM_DIR/doomgeneric/doomgeneric/"
42
+ cp "$DOOM_DIR/i_system.c" "$DOOM_DIR/doomgeneric/doomgeneric/"
41
43
 
42
44
  echo "Compiling DOOM to WebAssembly..."
43
45
  cd "$DOOM_DIR/doomgeneric/doomgeneric"
@@ -47,7 +49,7 @@ emcc -O2 \
47
49
  -s WASM=1 \
48
50
  -s USE_SDL=2 \
49
51
  -s EXPORTED_FUNCTIONS="['_doomgeneric_Create','_doomgeneric_Tick','_DG_GetFrameBuffer','_DG_PushKeyEvent','_malloc','_free']" \
50
- -s EXPORTED_RUNTIME_METHODS="['ccall','cwrap','getValue','setValue']" \
52
+ -s EXPORTED_RUNTIME_METHODS="['ccall','cwrap','getValue','setValue','FS']" \
51
53
  -s ALLOW_MEMORY_GROWTH=1 \
52
54
  -s INITIAL_MEMORY=33554432 \
53
55
  -s MODULARIZE=1 \
package/src/debug.ts ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Debug logging utility for OpenTUI-DOOM
3
+ *
4
+ * Only logs when DOOM_DEBUG environment variable is set.
5
+ * Usage: DOOM_DEBUG=1 bun run dev
6
+ */
7
+
8
+ import { appendFileSync } from "fs";
9
+ import { join } from "path";
10
+
11
+ const DEBUG_ENABLED = process.env.DOOM_DEBUG === "1" || process.env.DOOM_DEBUG === "true";
12
+ const logFile = join(import.meta.dir, "..", "debug.log");
13
+
14
+ /**
15
+ * Log a debug message to debug.log file if DOOM_DEBUG is enabled
16
+ */
17
+ export function debugLog(category: string, message: string): void {
18
+ if (!DEBUG_ENABLED) return;
19
+
20
+ const timestamp = new Date().toISOString();
21
+ const line = `[${timestamp}] [${category}] ${message}\n`;
22
+ try {
23
+ appendFileSync(logFile, line);
24
+ } catch (e) {
25
+ // Ignore logging errors
26
+ }
27
+ }
package/src/doom-audio.ts CHANGED
@@ -7,20 +7,13 @@
7
7
 
8
8
  import { spawn, ChildProcess } from "child_process";
9
9
  import { join } from "path";
10
- import { existsSync, appendFileSync, unlinkSync } from "fs";
10
+ import { existsSync, unlinkSync } from "fs";
11
11
  import { createConnection, Socket } from "net";
12
+ import { debugLog } from "./debug";
12
13
 
13
- // Log file for debugging
14
- const logFile = join(import.meta.dir, "..", "debug.log");
15
-
14
+ // Local helper to log with Audio category
16
15
  function log(message: string): void {
17
- const timestamp = new Date().toISOString();
18
- const line = `[${timestamp}] [Audio] ${message}\n`;
19
- try {
20
- appendFileSync(logFile, line);
21
- } catch (e) {
22
- // Ignore logging errors
23
- }
16
+ debugLog('Audio', message);
24
17
  }
25
18
 
26
19
  // Track all spawned mpv processes for cleanup