2 // PXT sound file player
3 // see bottom of file for info on how to use this module
6 #include <math.h> // for sin()
11 #include "../config.h"
17 #define MODEL_SIZE 256
18 #define PXCACHE_MAGICK 'PXC1'
20 // gets the next byte from wave "wave", scales it by the waves volume, and places result in "out".
21 // x * (y / z) = (x * y) / z
22 #define GETWAVEBYTE(wave, out) \
24 if (wave->model_no != MOD_WHITE) \
26 out = wave->model[(unsigned char)wave->phaseacc]; \
30 out = white[wave->white_ptr]; \
31 if (++wave->white_ptr >= WHITE_LEN) wave->white_ptr = 0; \
33 out *= wave->volume; \
38 #define WHITE_LEN 22050
39 int8_t white
[WHITE_LEN
];
41 // the final sounds ready to play (after pxt_PrepareToPlay)
47 void (*DoneCallback
)(int, int);
56 } wave
[PXT_NO_MODELS
];
59 static unsigned int rng_seed
= 0;
60 static unsigned short rand_next(void)
65 return (rng_seed
>> 16) & 0x7fff;
68 static void GenerateSineModel(unsigned char *table
)
70 double twopi
= 6.283184000f
;
71 double ratio
= 256.00f
;
72 double rat64
= 64.00f
;
84 table
[i
] = (unsigned char)reg
;
89 static void GenerateTriangleModel(unsigned char *table
)
93 for(i
=0;i
<64;i
++) table
[i
] = i
;
111 static void GenerateSawUpModel(unsigned char *table
)
116 table
[i
] = (i
>> 1) - 0x40;
120 static void GenerateSawDownModel(unsigned char *table
)
125 table
[i
] = 0x40 - (i
>> 1);
129 static void GenerateSquareModel(unsigned char *table
)
133 for(i
=0;i
<128;i
++) table
[i
] = 0x40;
134 for(;i
<256;i
++) table
[i
] = (uint8_t)-0x40;
138 static void GenerateRandModel(unsigned char *table
)
147 k
= (signed char)rand_next();
154 void GenerateWhiteModel(void)
158 seedrand(0xa42c1911);
160 for(i
=0;i
<WHITE_LEN
;i
++)
161 white
[i
] = random(-63, 63);
164 static void GeneratePulseModel(unsigned char *table
)
168 for(i
=0;i
<192;i
++) table
[i
] = 0x40;
169 for(;i
<256;i
++) table
[i
] = (uint8_t)-0x40;
173 // generate the models so we can do synth
174 // must call this before doing any rendering
177 static int inited
= 0;
182 staterr("pxt_init: pxt module already initialized");
187 memset(sound_fx
, 0, sizeof(sound_fx
));
188 for(i
=0;i
<256;i
++) sound_fx
[i
].channel
= -1;
193 char pxt_initsynth(void)
195 static int synth_inited
= 0;
196 if (synth_inited
) return 0; else synth_inited
= 1;
198 GenerateSineModel(wave
[MOD_SINE
].table
);
199 GenerateTriangleModel(wave
[MOD_TRI
].table
);
200 GenerateSawUpModel(wave
[MOD_SAWUP
].table
);
201 GenerateSawDownModel(wave
[MOD_SAWDOWN
].table
);
202 GenerateSquareModel(wave
[MOD_SQUARE
].table
);
203 GenerateRandModel(wave
[MOD_NOISE
].table
);
204 GeneratePulseModel(wave
[MOD_PULSE
].table
);
205 GenerateWhiteModel();
209 char pxt_SetModel(stPXWave
*pxwave
, int m
)
211 if (m
>= 0 && m
< PXT_NO_MODELS
)
213 pxwave
->model
= (signed char *)wave
[m
].table
;
214 pxwave
->model_no
= m
;
219 staterr("pxt_SetModel: invalid sound model '%d'", m
);
230 sprintf(buf, "%d", val);
231 for(i=0;i<4-strlen(buf);i++) lprintf(" ");
234 if (++c > 16) {c=0; lprintf("\n");}
243 sprintf(buf, "%02x", val);
245 lprintf("%s ", &buf[i]);
247 if (++c > 24) { c=0; lprintf("\n"); }
252 /*static void display_audio(signed char *buffer, int size_blocks, int centerline, int ysize, char *caption, char is_env, int r, int g, int b)
254 double ratio, yratio;
263 #define scale_sample(value) (centerline + (int)((double)value * yratio))
265 //lprintf("display_audio: displaying buffer of len %d\n", size_blocks);
267 ratio = (double)size_blocks / (double)wd;
268 yratio = (double)ysize / (double)(127+127);
269 //lprintf("ratio = %.2f yratio = %.2f\n", ratio, yratio);
271 DrawSDLLine(SCREEN_WIDTH/2, 0, SCREEN_WIDTH/2, SCREEN_HEIGHT, 18,18,18);
272 DrawSDLLine(xoff, centerline, xoff+wd, centerline, 255,0,0);
273 y = scale_sample(TOPAMP); DrawSDLLine(xoff, y, xoff+wd, y, 68,68,68);
274 y = scale_sample(BTMAMP); DrawSDLLine(xoff, y, xoff+wd, y, 68,68,68);
278 for(x=xoff;x<wd+xoff;x++)
280 value = buffer[(int)curpos];
283 // envelope is 0-63, so scale it to 0-127
284 if (is_env) value = -value * 2;
286 y = scale_sample(value);
288 if (lastx==-1 || abs(lasty - y) < 2)
290 PlotSDLPixel(x, y, r,g,b);
294 DrawSDLLine(lastx, lasty, x, y, r,g,b);
297 lastx = x; lasty = y;
302 sprintf(buf, " %s len = %d", caption, size_blocks);
303 font_draw_shaded(4, ((centerline+(ysize/2))-16), buf, 0, &greenfont);
309 void debugshowsound(stPXSound *snd)
311 uchar r[4] = { 250, 49, 250, 0 };
312 uchar g[4] = { 250, 179, 127, 200 };
313 uchar b[4] = { 0, 49, 127, 180 };
315 char *capt1 = "", *capt2 = "";
319 if (snd->chan[c].enabled)
321 display_audio(snd->chan[c].buffer, snd->chan[c].size_blocks, 60, 80, capt1, 0, r[c], g[c], b[c]);
322 display_audio(snd->chan[c].envbuffer, 256, 170, 80, capt2, 1, r[c], g[c], b[c]);
336 // sets the given envelope to default values
337 void pxt_SetDefaultEnvelope(stPXEnvelope
*env
)
349 // generate a 256-byte envelope "waveform" containing volume adjustment values from 00-3f
350 // in short it renders the envelope for a sound.
351 // the envelope must be ready before CreateAudio can be used.
352 void GenerateEnvelope(stPXEnvelope
*env
, char *buffer
)
354 double curenv
, envinc
;
357 curenv
= env
->initial
;
358 envinc
= (double)(env
->val
[0] - env
->initial
) / env
->time
[0];
359 for(i
=0;i
<env
->time
[0];i
++)
361 buffer
[i
] = (int)curenv
;
365 curenv
= env
->val
[0];
366 envinc
= (double)(env
->val
[1] - env
->val
[0]) / (env
->time
[1] - env
->time
[0]);
367 for(;i
<env
->time
[1];i
++)
369 buffer
[i
] = (int)curenv
;
373 curenv
= env
->val
[1];
374 envinc
= (double)(env
->val
[2] - env
->val
[1]) / (env
->time
[2] - env
->time
[1]);
375 for(;i
<env
->time
[2];i
++)
377 buffer
[i
] = (int)curenv
;
381 // fade to 0 volume if time_c is < end of sound, just like the pretty drawing in PixTone.
382 envinc
= (double)(-1 - env
->val
[2]) / (256 - env
->time
[2]);
383 curenv
= env
->val
[2];
386 buffer
[i
] = (int)curenv
;
393 // added this for sound editing tools. it will render you a single PXWave,
394 // that is a component of a PXSound, into a buffer you provide.
395 // otherwise it's useless.
396 void pxt_RenderPXWave(stPXWave
*pxwave
, signed char *buffer
, int size_blocks
)
401 // we generate twice the buf size and average it down afterwards for increased precision.
402 // this is what pixtone does, and although I'm not sure if that's why; it really does
403 // seem to make a slight quality increase
405 char *tempbuffer
= (char *)malloc(size_blocks
);
407 //lprintf("RenderPXWave: buffer len %d, repeat = %.2f\n", size_blocks, pxwave->repeat);
409 pxwave
->phaseinc
= ((MODEL_SIZE
* pxwave
->repeat
) / (double)size_blocks
);
410 pxwave
->phaseacc
= (double)pxwave
->offset
;
411 pxwave
->white_ptr
= pxwave
->offset
;
413 for(i
=0;i
<size_blocks
;i
++)
415 GETWAVEBYTE(pxwave
, output
);
417 tempbuffer
[i
] = output
;
418 pxwave
->phaseacc
+= pxwave
->phaseinc
;
421 // average our doublesampled audio down into the final buffer
422 for(i
=j
=0;i
<size_blocks
;i
+=2)
424 e
= tempbuffer
[i
] + tempbuffer
[i
+1];
431 // renders a sound channel.
432 // call GenerateEnvelope first.
433 static void CreateAudio(stPXChannel
*chan
)
435 // store all the commonly-used pointers
436 stPXWave
*main
= &chan
->main
;
437 stPXWave
*pitch
= &chan
->pitch
;
438 stPXWave
*pitch2
= &chan
->pitch2
;
439 stPXWave
*volume
= &chan
->volume
;
440 unsigned char *envbuffer
= chan
->envbuffer
;
441 int size_blocks
= chan
->size_blocks
;
444 int output
, bm
, bm2
, volmod
;
447 double env_acc
, env_inc
;
449 // we generate twice the buf size and average it down afterwards for increased precision.
450 // this is what pixtone does, and although I'm not sure if that's why; it really does
451 // seem to make a slight quality increase
453 signed char *buffer
= (signed char *)malloc(size_blocks
);
455 //lprintf("CreateAudio: buffer len %d, repeat = %.2f / %.2f\n", size_blocks, main->repeat, pitch->repeat);
457 // calculate all the phaseinc's
458 main
->phaseinc
= ((MODEL_SIZE
* main
->repeat
) / (double)size_blocks
);
459 pitch
->phaseinc
= ((MODEL_SIZE
* pitch
->repeat
) / (double)size_blocks
);
460 pitch2
->phaseinc
= ((MODEL_SIZE
* pitch2
->repeat
) / (double)size_blocks
);
461 volume
->phaseinc
= ((MODEL_SIZE
* volume
->repeat
) / (double)size_blocks
);
462 env_inc
= (MODEL_SIZE
/ (double)size_blocks
);
464 //lprintf("main phaseinc = %.6f\n", main->phaseinc);
465 //lprintf("pitch phaseinc = %.6f\n", pitch->phaseinc);
466 //lprintf("volume phaseinc = %.6f\n", volume->phaseinc);
468 // set the starting positions
469 main
->phaseacc
= (double)main
->offset
;
470 pitch
->phaseacc
= (double)pitch
->offset
;
471 pitch2
->phaseacc
= (double)pitch2
->offset
;
472 volume
->phaseacc
= (double)volume
->offset
;
474 main
->white_ptr
= main
->offset
;
475 pitch
->white_ptr
= pitch
->offset
;
476 pitch2
->white_ptr
= pitch2
->offset
;
477 volume
->white_ptr
= volume
->offset
;
481 for(i
=0;i
<size_blocks
;i
++)
483 // get next sample from the main waveform
484 GETWAVEBYTE(main
, output
);
485 GETWAVEBYTE(volume
, volmod
);
487 // hack to allow inverting sign of MOD_PULSE-based volume modulators
488 // by just raising the top over 128
489 if (volume
->model_no
==MOD_PULSE
)
491 if (volmod
> 127 || volmod
< -127)
495 volmod
= 256 - volmod
;
499 volmod
= -(256 - -volmod
);
505 // offset volume modulator such that it completely silences the sound at <= -0x40
507 if (volmod
< 0) volmod
= 0;
509 // apply volume modulator to main
510 output
= (output
* volmod
) / 64;
512 // apply the envelope
513 e
= envbuffer
[(unsigned char)env_acc
];
514 output
= (output
* e
) / 64;
516 // save sample to output buffer
520 // get next byte from the frequency modulator waveform
521 GETWAVEBYTE(pitch
, bm
);
522 GETWAVEBYTE(pitch2
, bm2
);
524 // clip the values to a signed char if it's a pulse waveform...allows
525 // switch it's signed from up/up/down to down/down/up just by taking
526 // the top over 128. but for other waveforms we actually want the clipping
527 // to not happen right in this case because A. it allows for making larger
528 // range frequency sweeps than otherwise possible and B. pixtone has this
529 // same "glitch" so it's a compatibility thing.
530 if (pitch
->model_no
==MOD_PULSE
) bm
= (signed char)bm
;
531 if (pitch2
->model_no
==MOD_PULSE
) bm2
= (signed char)bm2
;
537 { // when positive, every 32 clicks doubles the phaseinc.
538 // this is actually "phaseval = (mod / 32) * main->phaseinc;" however
539 // we lose precision by doing the division first, so this is equivalent:
540 phaseval
= ((double)bm
* main
->phaseinc
) / 32;
542 // phaseinc is sped up by phaseval
543 main
->phaseacc
+= (main
->phaseinc
+ phaseval
);
546 { // when negative, every 64 clicks half's the phaseinc.
547 // this can be thought of as "phaseval = (mod / 64) * (main->phaseinc * 0.5f);" however
548 // that doesn't actually work because of precision loss, so this instead:
549 phaseval
= ((double)(-bm
) * main
->phaseinc
) / 128;
551 // phaseinc is slowed down by phaseval
552 main
->phaseacc
+= (main
->phaseinc
- phaseval
);
556 pitch
->phaseacc
+= pitch
->phaseinc
;
557 pitch2
->phaseacc
+= pitch2
->phaseinc
;
558 volume
->phaseacc
+= volume
->phaseinc
;
561 // just to make certain the envelope never starts over from the beginning.
562 // although it shouldn't; the precision on env_inc seems to be pretty good.
563 if (env_acc
> 255) env_acc
= 255;
565 // normally we would have to do this, but we don't as long as model_size is 256
566 // and we cast phaseacc to an unsigned char when we do the lookup; it'll wrap by itself.
568 //while(main->phaseacc >= MODEL_SIZE) main->phaseacc -= MODEL_SIZE;
569 //while(pitch->phaseacc >= MODEL_SIZE) pitch->phaseacc -= MODEL_SIZE;
572 // average our doublesampled audio down into the final buffer
573 signed char *outbuffer
= chan
->buffer
;
574 for(i
=j
=0;i
<size_blocks
;i
+=2)
576 e
= buffer
[i
] + buffer
[i
+1];
583 // allocate all the buffers needed for the given sound
584 static char AllocBuffers(stPXSound
*snd
)
591 // allocate buffers for each enabled channel
592 for(i
=0;i
<PXT_NO_CHANNELS
;i
++)
594 if (snd
->chan
[i
].enabled
)
596 snd
->chan
[i
].buffer
= (signed char *)malloc(snd
->chan
[i
].size_blocks
);
597 if (!snd
->chan
[i
].buffer
)
599 staterr("AllocBuffers (pxt): out of memory (1)!");
603 if (snd
->chan
[i
].size_blocks
> topbufsize
)
604 topbufsize
= snd
->chan
[i
].size_blocks
;
608 // allocate the final buffer
609 snd
->final_buffer
= (signed char *)malloc(topbufsize
);
610 if (!snd
->final_buffer
)
612 staterr("AllocBuffers (pxt): out of memory (2)!");
616 snd
->final_size
= topbufsize
;
622 // generate 8-bit signed PCM audio from a PXT sound, put it in it's final_buffer.
623 char pxt_Render(stPXSound
*snd
)
626 signed short mixed_sample
;
627 signed short *middle_buffer
;
630 bufsize
= AllocBuffers(snd
);
631 if (bufsize
==-1) return 1; // error
633 // --------------------------------
634 // render all the channels
635 // --------------------------------
636 for(i
=0;i
<PXT_NO_CHANNELS
;i
++)
638 if (snd
->chan
[i
].enabled
)
640 // memset(snd->chan[i].buffer, 0, sizeof(snd->chan[i].buffer));
641 GenerateEnvelope(&snd
->chan
[i
].envelope
, (char *)snd
->chan
[i
].envbuffer
);
642 CreateAudio(&snd
->chan
[i
]);
646 // ----------------------------------------------
647 // mix the channels [generate final_buffer]
648 // ----------------------------------------------
649 //lprintf("final_size = %d final_buffer = %08x\n", snd->final_size, snd->final_buffer);
651 middle_buffer
= (signed short *)malloc(snd
->final_size
* 2);
652 memset(middle_buffer
, 0, snd
->final_size
* 2);
654 for(i
=0;i
<PXT_NO_CHANNELS
;i
++)
656 if (snd
->chan
[i
].enabled
)
658 for(s
=0;s
<snd
->chan
[i
].size_blocks
;s
++)
660 middle_buffer
[s
] += snd
->chan
[i
].buffer
[s
];
665 for(s
=0;s
<snd
->final_size
;s
++)
667 mixed_sample
= middle_buffer
[s
];
669 if (mixed_sample
> 127) mixed_sample
= 127;
670 else if (mixed_sample
< -127) mixed_sample
= -127;
672 snd
->final_buffer
[s
] = (char)mixed_sample
;
680 // get an already-rendered pxt 'snd' ready for sending to SDL_mixer.
681 // converts the 8-bit signed audio to SDL_mixer's 16-bit stereo format and
682 // sets up the Mix_Chunk.
683 void pxt_PrepareToPlay(stPXSound
*snd
, int slot
)
688 signed char *buffer
= snd
->final_buffer
;
689 signed short *outbuffer
;
692 // convert the buffer from 8-bit mono signed to 16-bit stereo signed
693 malc_size
= (snd
->final_size
* 2 * 2);
694 outbuffer
= (signed short *)malloc(malc_size
);
696 for(i
=ap
=0;i
<snd
->final_size
;i
++)
700 value
= htole16(value
);
702 outbuffer
[ap
++] = value
; // left ch
703 outbuffer
[ap
++] = value
; // right ch
707 sound_fx
[slot
].buffer
= outbuffer
;
708 sound_fx
[slot
].len
= snd
->final_size
;
709 //lprintf("pxt ready to play in slot %d\n", slot);
712 // quick-and-dirty function to raise or lower the pitch of a sound.
713 // I say quick-and-dirty because it also changes the length.
714 // We need this for the "SSS" (Stream Sound) which is supposed to
715 // have adjustable pitch.
716 void pxt_ChangePitch(stPXSound
*snd
, double factor
)
718 signed char *inbuffer
= snd
->final_buffer
;
719 int insize
= snd
->final_size
;
721 int outsize
= (int)((double)insize
* factor
);
722 signed char *outbuffer
= (signed char *)malloc(outsize
);
723 if (factor
== 0) factor
= 0.001;
725 for(int i
=0;i
<outsize
;i
++)
726 outbuffer
[i
] = inbuffer
[(int)((double)i
/ factor
)];
728 free(snd
->final_buffer
);
729 snd
->final_buffer
= outbuffer
;
730 snd
->final_size
= outsize
;
734 // begins playing the pxt in the given slot.
735 // the SSChannel is returned.
736 // on error, returns -1.
737 int pxt_Play(int chan
, int slot
, char loop
)
739 return pxt_PlayWithCallback(chan
, slot
, loop
, NULL
);
742 int pxt_PlayWithCallback(int chan
, int slot
, char loop
, void (*FinishedCB
)(int, int))
744 if (sound_fx
[slot
].buffer
)
746 // locking the audio here ensures that sound won't finish before we get down
747 // below and finish setting it's params.
751 chan
= SSPlayChunk(chan
, sound_fx
[slot
].buffer
, sound_fx
[slot
].len
, slot
, pxtLooper
);
752 SSEnqueueChunk(chan
, sound_fx
[slot
].buffer
, sound_fx
[slot
].len
, slot
, pxtLooper
);
754 sound_fx
[slot
].loops_left
= (loop
==-1) ? -1 : (loop
- 1);
758 chan
= SSPlayChunk(chan
, sound_fx
[slot
].buffer
, sound_fx
[slot
].len
, slot
, pxtSoundDone
);
761 sound_fx
[slot
].DoneCallback
= FinishedCB
;
762 sound_fx
[slot
].channel
= chan
;
767 staterr("pxt_Play: SSPlayChunk returned error");
773 staterr("pxt_Play: sound slot 0x%02x not rendered", slot
);
778 static void pxtSoundDone(int chan
, int slot
)
780 sound_fx
[slot
].channel
= -1;
781 if (sound_fx
[slot
].DoneCallback
)
783 (*sound_fx
[slot
].DoneCallback
)(chan
, slot
);
787 static void pxtLooper(int chan
, int slot
)
789 if (sound_fx
[slot
].loops_left
)
791 SSEnqueueChunk(chan
, sound_fx
[slot
].buffer
, sound_fx
[slot
].len
, slot
, pxtLooper
);
795 pxtSoundDone(chan
, slot
);
798 if (sound_fx
[slot
].loops_left
> 0) sound_fx
[slot
].loops_left
--;
801 void pxt_Stop(int slot
)
802 { /// possible threading issues here? i'm not sure if it's important enough
803 /// i don't want to lock the audio because i'm worried that when the sound is aborted
804 /// it could end up being left locked during the user's sound done callback.
805 if (sound_fx
[slot
].channel
!= -1)
807 sound_fx
[slot
].loops_left
= 0;
808 SSAbortChannel(sound_fx
[slot
].channel
);
812 char pxt_IsPlaying(int slot
)
814 return (sound_fx
[slot
].channel
!= -1);
818 // render all pxt files under "path" up to slot "top".
819 // get them all ready to play in their sound slots.
820 // if cache_name is specified the pcm audio data is cached under the given filename.
821 char pxt_LoadSoundFX(const char *path
, const char *cache_name
, int top
)
828 stat("Loading Sound FX...");
833 // try to load the cache if we can
834 if (LoadFXCache(cache_name
, top
) == 0)
839 fp
= fileopen(cache_name
, "wb");
842 staterr("LoadSoundFX: failed open: '%s'", cache_name
);
846 uint32_t magick
= PXCACHE_MAGICK
;
847 fwrite(&magick
, 4, 1, fp
); // fwrite allows us to verify endianness, as well
851 // get ready to do synthesis
854 for(slot
=1;slot
<=top
;slot
++)
856 sprintf(fname
, "%sfx%02x.pxt", path
, slot
);
858 if (pxt_load(fname
, &snd
)) continue;
861 // dirty hack; lower the pitch of the Stream Sounds
862 // to match the way they actually sound in the game
863 // with the SSS0400 command.
865 pxt_ChangePitch(&snd
, 5.0f
);
867 pxt_ChangePitch(&snd
, 6.0f
);
869 // save the rendered audio to cache
872 fputl(snd
.final_size
, fp
);
874 fwrite(snd
.final_buffer
, snd
.final_size
, 1, fp
);
877 // upscale the sound to 16-bit for SDL_mixer then throw away the now unnecessary 8-bit data
878 pxt_PrepareToPlay(&snd
, slot
);
884 stat(" - created %s; %d bytes", cache_name
, ftell(fp
));
892 // attempts to load all the PXT's out of the given cache file.
893 // if succesful, returns 0.
894 static char LoadFXCache(const char *fname
, int top
)
901 fp
= fileopen(fname
, "rb");
904 stat("LoadFXCache: audio cache %s not exist", fname
);
908 // I don't use endian-agnostic fgetl as this file is endian-specific and we
909 // want the check to fail if the file were moved from a little-endian to
910 // big-endian system or vice-versa.
911 fread(&magick
, sizeof(magick
), 1, fp
);
912 if (magick
!= PXCACHE_MAGICK
)
914 stat("LoadFXCache: %s is incorrect format: expected %08x, got %08x", fname
, PXCACHE_MAGICK
, magick
);
919 if (fgeti(fp
) != top
)
921 stat("LoadFXCache: # of sounds has changed since cache creation");
927 snd
.final_buffer
= NULL
;
929 stat("LoadFXCache: restoring pxts from cache");
932 snd
.final_size
= fgetl(fp
);
934 if (slot
== -1) break;
936 if (snd
.final_size
> allocd_size
)
938 allocd_size
= (snd
.final_size
* 10);
940 if (snd
.final_buffer
) free(snd
.final_buffer
);
941 snd
.final_buffer
= (signed char *)malloc(allocd_size
);
942 if (!snd
.final_buffer
)
944 staterr("LoadFXCache: out of memory!");
949 fread(snd
.final_buffer
, snd
.final_size
, 1, fp
);
950 pxt_PrepareToPlay(&snd
, slot
);
954 free(snd
.final_buffer
);
958 void pxt_freeSoundFX(void)
961 for(i
=0;i
<=load_top
;i
++)
963 if (sound_fx
[i
].buffer
)
965 free(sound_fx
[i
].buffer
);
966 sound_fx
[i
].buffer
= NULL
;
971 void pxt_FreeSound(int slot
)
973 if (sound_fx
[slot
].buffer
)
975 free(sound_fx
[slot
].buffer
);
976 sound_fx
[slot
].buffer
= NULL
;
985 //lprintf("Loading sound FX...\n");
986 start = SDL_GetTicks();
987 if (LoadSoundFX("pxt/", "fx.pcm", 0xa0)) return 1;
988 lprintf("time = %d\n", SDL_GetTicks() - start);
992 snd = pxt_load("pxt/fx11.pxt");
997 pxt_PrepareToPlay(snd, 4);
1000 debugshowsound(snd);
1007 // free all the internal buffers of a PXSound
1008 void FreePXTBuf(stPXSound
*snd
)
1014 // free up the buffers
1015 for(i
=0;i
<PXT_NO_CHANNELS
;i
++)
1017 if (snd
->chan
[i
].buffer
)
1019 free(snd
->chan
[i
].buffer
);
1020 snd
->chan
[i
].buffer
= NULL
;
1024 if (snd
->final_buffer
)
1026 free(snd
->final_buffer
);
1027 snd
->final_buffer
= NULL
;
1034 // read a .pxt file into memory and return a stPXSound ready to be rendered.
1035 char pxt_load(const char *fname
, stPXSound
*snd
)
1038 char load_extended_section
= 0;
1042 #define BRACK '{' // my damn IDE is borking up the Function List if i put this inline
1044 fp
= fileopen(fname
, "rb");
1045 if (!fp
) { staterr("pxt_load: file '%s' not found.", fname
); return 1; }
1047 //lprintf("pxt_load: reading %s...\n", fname);
1049 // set all buffers to non-existant and zero-length to start
1050 memset(snd
, 0, sizeof(stPXSound
));
1053 // skip ahead in the file till we find the machine readable data, denoted by '{'
1054 if (ReadToBracket(fp
)) return 1;
1056 // load all channels we find
1061 if (ch
=='>') // extended section
1063 load_extended_section
= 1;
1067 { // opening a new channel
1068 if (cc
>= PXT_NO_CHANNELS
)
1070 staterr("pxt_load: sound '%s' contains too many channels!", fname
);
1074 snd
->chan
[cc
].enabled
= fgeticsv(fp
);
1075 snd
->chan
[cc
].size_blocks
= fgeticsv(fp
);
1077 if (LoadComponent(fp
, &snd
->chan
[cc
].main
)) goto error
;
1078 if (LoadComponent(fp
, &snd
->chan
[cc
].pitch
)) goto error
;
1079 if (LoadComponent(fp
, &snd
->chan
[cc
].volume
)) goto error
;
1081 snd
->chan
[cc
].envelope
.initial
= fgeticsv(fp
);
1082 for(i
=0;i
<PXENV_NUM_VERTICES
;i
++)
1084 snd
->chan
[cc
].envelope
.time
[i
] = fgeticsv(fp
);
1085 snd
->chan
[cc
].envelope
.val
[i
] = fgeticsv(fp
);
1094 if (load_extended_section
)
1096 stat("pxt_load: extended section found, loading it");
1097 if (ReadToBracket(fp
)) return 1;
1098 for(cc
=0;cc
<PXT_NO_CHANNELS
;cc
++)
1100 if (LoadComponent(fp
, &snd
->chan
[cc
].pitch2
)) goto error
;
1105 //stat("No extended section found; setting compatibility values.");
1106 for(i
=0;i
<PXT_NO_CHANNELS
;i
++)
1108 memset(&snd
->chan
[i
].pitch2
, 0, sizeof(stPXWave
));
1109 pxt_SetModel(&snd
->chan
[i
].pitch2
, 0);
1113 //stat("pxt_load: '%s' parsed ok", fname);
1118 for(i
=0;i
<PXT_NO_CHANNELS
;i
++)
1120 if (snd
->chan
[i
].buffer
)
1122 free(snd
->chan
[i
].buffer
);
1123 snd
->chan
[i
].buffer
= NULL
;
1131 static char LoadComponent(FILE *fp
, stPXWave
*pxw
)
1133 if (pxt_SetModel(pxw
, fgeticsv(fp
))) return 1;
1135 pxw
->repeat
= fgetfcsv(fp
);
1136 pxw
->volume
= fgeticsv(fp
);
1137 pxw
->offset
= fgeticsv(fp
);
1141 static char ReadToBracket(FILE *fp
)
1148 if (ch
==BRACK
) break;
1152 staterr("pxt_load: file is in incorrect file format [failed to find '{']");
1161 char pxt_save(const char *fname
, stPXSound
*snd
)
1166 fp
= fileopen(fname
, "wb");
1169 stat("save_pxt: unable to open '%s'", fname
);
1173 for(i
=0;i
<PXT_NO_CHANNELS
;i
++)
1175 fprintf(fp
, "use :%d\r\n", snd
->chan
[i
].enabled
);
1176 fprintf(fp
, "size :%d\r\n", snd
->chan
[i
].size_blocks
);
1178 SaveComponent(fp
, "main", &snd
->chan
[i
].main
);
1179 SaveComponent(fp
, "pitch", &snd
->chan
[i
].pitch
);
1180 SaveComponent(fp
, "volume", &snd
->chan
[i
].volume
);
1182 fprintf(fp
, "initialY:%d\r\n", snd
->chan
[i
].envelope
.initial
);
1183 for(j
=0;j
<PXENV_NUM_VERTICES
;j
++)
1185 SaveEnvVertice(fp
, &snd
->chan
[i
].envelope
, j
);
1188 fprintf(fp
, "\r\n");
1191 // save "machine-readable" section
1192 for(i
=0;i
<PXT_NO_CHANNELS
;i
++)
1196 fprintf(fp
, "%d,%d,", snd
->chan
[i
].enabled
, snd
->chan
[i
].size_blocks
);
1198 SaveComponentMachine(fp
, &snd
->chan
[i
].main
, 1);
1199 SaveComponentMachine(fp
, &snd
->chan
[i
].pitch
, 1);
1200 SaveComponentMachine(fp
, &snd
->chan
[i
].volume
, 1);
1202 fprintf(fp
, "%d,", snd
->chan
[i
].envelope
.initial
);
1203 for(j
=0;j
<PXENV_NUM_VERTICES
-1;j
++)
1205 fprintf(fp
, "%d,%d,", snd
->chan
[i
].envelope
.time
[j
], snd
->chan
[i
].envelope
.val
[j
]);
1207 fprintf(fp
, "%d,%d", snd
->chan
[i
].envelope
.time
[j
], snd
->chan
[i
].envelope
.val
[j
]);
1209 fprintf(fp
, "},\r\n");
1212 // save "extended" section-- original PixTone seems really picky about
1213 // trying to put anything it doesn't know about into the normal section
1215 // machine readable pitch2
1216 fprintf(fp
, "\r\n-> {");
1217 for(i
=0;i
<PXT_NO_CHANNELS
;i
++)
1219 SaveComponentMachine(fp
, &snd
->chan
[i
].pitch2
, (i
+1<PXT_NO_CHANNELS
));
1221 fprintf(fp
, "},\r\n");
1223 // machine readable extra envelope vertices
1224 fprintf(fp
, "-> {255,0,255,0,255,0,255,0},\r\n");
1226 // human readable copy
1227 fprintf(fp
, "\r\n");
1228 for(i
=0;i
<PXT_NO_CHANNELS
;i
++)
1230 SaveComponent(fp
, "pitch2", &snd
->chan
[i
].pitch
);
1231 fprintf(fp
, "dx :255\r\n");
1232 fprintf(fp
, "dy :0\r\n");
1233 fprintf(fp
, "\r\n");
1237 stat("pxt save ok '%s'", fname
);
1242 static void SaveComponent(FILE *fp
, const char *name
, stPXWave
*pxw
)
1247 nspaces
= 6 - strlen(name
);
1248 if (nspaces
) memset(spaces
, ' ', nspaces
);
1249 spaces
[nspaces
] = 0;
1251 fprintf(fp
, "%s_model %s:%d\r\n", name
, spaces
, pxw
->model_no
);
1252 fprintf(fp
, "%s_freq %s:%.2f\r\n", name
, spaces
, pxw
->repeat
);
1253 fprintf(fp
, "%s_top %s:%d\r\n", name
, spaces
, pxw
->volume
);
1254 fprintf(fp
, "%s_offset%s:%d\r\n", name
, spaces
, pxw
->offset
);
1257 static void SaveComponentMachine(FILE *fp
, stPXWave
*pxw
, char trailcomma
)
1259 fprintf(fp
, "%d,%.2f,%d,%d", pxw
->model_no
, pxw
->repeat
, pxw
->volume
, pxw
->offset
);
1260 if (trailcomma
) fprintf(fp
, ",");
1263 static void SaveEnvVertice(FILE *fp
, stPXEnvelope
*env
, int v
)
1265 fprintf(fp
, "%cx :%d\r\n", v
+'a', env
->time
[v
]);
1266 fprintf(fp
, "%cy :%d\r\n", v
+'a', env
->val
[v
]);
1270 how to use it--it's pretty easy
1272 First you have to load (parse) the pxt file you want to play.
1273 You can do this with pxt_load() which will read a pxt file and set up your stPXSound
1274 structure with the proper values as spec'd in the file.
1276 Then you must synthesize or *render* the sound. First make sure the synthesizer
1277 is initialized by calling pxt_init & pxt_initsynth.
1279 Send your stPXSound through render_pxt. Now it includes 8-bit signed PCM audio
1282 But maybe you want it in a format SDL_mixer can play easier? No problem, call pxt_PrepareToPlay.
1283 Give it your sound and a *slot number* from 0-255 which is like a sound id as would be used in
1284 a game. You can free (pxt_freebuffers) that old 8-bit data now if you like and in fact entirely
1285 throw away the stPXSound as it has nothing to do with pxt_Play. When you're ready to
1286 play the sound give pxt_Play the slot number you picked and it will return to you the
1287 SDL_mixer channel number if successful.
1289 Oh yeah, you can also load a whole directory full of pxt's, up to some max slot #,
1290 by using pxt_LoadSoundFX. This function also has the bonus that first, you don't have to
1291 do any of the above stuff, it does it all for you and you can just call pxt_Play straight away.
1292 Secondly, you can give it a filename for a cache file and it will cache all the sounds after
1293 it builds them so that they load quicker next time.