외부 사운드 파일 없는 C 언어 절차적 파형 합성 및 44바이트 WAV 헤더 직렬화Procedural Audio Waveform Synthesis and 44-Byte WAV Header Serialization in C
외부 .wav/.ogg 에셋 파일에 의존하지 않고 C 언어 삼각함수(sinf), 지수 감쇠 엔벨로프, LFSR 의사 난수 노이즈로 16비트 PCM 샘플을 합성하여 RIFF/WAVE 메모리 버퍼로 직렬화하는 기법입니다.Synthesizing 16-bit PCM audio samples using C math functions (sinf, exponential decay envelopes, LFSR noise) and serializing a 44-byte RIFF/WAVE header in memory without external sound files.
sinf()와 선형 피드백 시프트 레지스터(LFSR)로 16비트 부호 있는 정수(int16_t) PCM 파형을 계산하고 앞에 44바이트 표준 RIFF/WAVE 헤더를 붙이면 메모리 상에서 즉시 재생 가능한 사운드 에셋을 생성할 수 있습니다..wav or .ogg files adds deployment complexity. Because digital audio is simply a time-series array of air pressure values, you can compute 16-bit signed integer (int16_t) PCM waveforms using sinf() and Linear Feedback Shift Registers (LFSR), prepend a 44-byte standard RIFF/WAVE header, and play the resulting memory buffer directly.1. 44바이트 RIFF/WAVE 헤더 구조체 및 주파수 스윕 PCM 합성기 구현1. Implementing the 44-Byte RIFF/WAVE Header and Frequency-Sweep PCM Synthesizer
샘플 레이트 44,100Hz, 단일 채널(Mono), 16비트 PCM 규격에 맞춰 44바이트 헤더를 패딩 없이 기록하고, 위상 누적기(phase)를 적분하여 주파수가 급격히 변할 때도 파형이 끊기지 않는 연속 사인파/구형파 샘플을 생성합니다.
Pack the 44-byte header for 44,100Hz mono 16-bit PCM without struct padding, and integrate a phase accumulator (phase) so frequency sweeps produce smooth waveforms without phase discontinuities.
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define SAMPLE_RATE 44100
#define TWO_PI 6.28318530718f
#pragma pack(push, 1)
typedef struct {
char riff_id[4]; // "RIFF"
uint32_t file_size; // 36 + data_size
char wave_id[4]; // "WAVE"
char fmt_id[4]; // "fmt "
uint32_t fmt_size; // 16 (PCM)
uint16_t audio_format; // 1 (Linear Quantization)
uint16_t num_channels; // 1 (Mono)
uint32_t sample_rate; // 44100
uint32_t byte_rate; // sample_rate * num_channels * 2
uint16_t block_align; // num_channels * 2
uint16_t bits_per_sample; // 16
char data_id[4]; // "data"
uint32_t data_size; // num_samples * 2
} WavHeader44;
#pragma pack(pop)
// 메모리 버퍼에 44바이트 WAV 헤더 + 16비트 PCM 레이저/점프 효과음 생성
uint8_t* BuildProceduralSfxWav(float duration_sec, float start_hz, float end_hz, size_t *out_bytes) {
uint32_t num_samples = (uint32_t)(SAMPLE_RATE * duration_sec);
uint32_t data_size = num_samples * sizeof(int16_t);
size_t total_bytes = sizeof(WavHeader44) + data_size;
uint8_t *buffer = (uint8_t *)malloc(total_bytes);
if (!buffer) return NULL;
WavHeader44 hdr = {
.riff_id = {'R','I','F','F'}, .file_size = 36 + data_size,
.wave_id = {'W','A','V','E'}, .fmt_id = {'f','m','t',' '},
.fmt_size = 16, .audio_format = 1, .num_channels = 1,
.sample_rate = SAMPLE_RATE, .byte_rate = SAMPLE_RATE * 2,
.block_align = 2, .bits_per_sample = 16,
.data_id = {'d','a','t','a'}, .data_size = data_size
};
memcpy(buffer, &hdr, sizeof(WavHeader44));
int16_t *pcm = (int16_t *)(buffer + sizeof(WavHeader44));
float phase = 0.0f;
for (uint32_t i = 0; i < num_samples; ++i) {
float t = (float)i / (float)num_samples; // 정규화 진행률 (0.0 ~ 1.0)
float freq = start_hz + (end_hz - start_hz) * t;
float env = expf(-5.0f * t); // 지수 감쇠 볼륨 엔벨로프
phase += (TWO_PI * freq) / (float)SAMPLE_RATE;
if (phase >= TWO_PI) phase -= TWO_PI;
float sample_f = sinf(phase) * env;
pcm[i] = (int16_t)(sample_f * 28000.0f); // 16비트 클리핑 방지 여유폭 확보
}
if (out_bytes) *out_bytes = total_bytes;
return buffer; // raylib LoadWaveFromMemory(".wav", buffer, total_bytes)에 즉시 전달 가능
}
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define SAMPLE_RATE 44100
#define TWO_PI 6.28318530718f
#pragma pack(push, 1)
typedef struct {
char riff_id[4]; // "RIFF"
uint32_t file_size; // 36 + data_size
char wave_id[4]; // "WAVE"
char fmt_id[4]; // "fmt "
uint32_t fmt_size; // 16 (PCM)
uint16_t audio_format; // 1 (Linear Quantization)
uint16_t num_channels; // 1 (Mono)
uint32_t sample_rate; // 44100
uint32_t byte_rate; // sample_rate * num_channels * 2
uint16_t block_align; // num_channels * 2
uint16_t bits_per_sample; // 16
char data_id[4]; // "data"
uint32_t data_size; // num_samples * 2
} WavHeader44;
#pragma pack(pop)
// Generate a 44-byte WAV header + 16-bit PCM laser/jump sound effect in memory
uint8_t* BuildProceduralSfxWav(float duration_sec, float start_hz, float end_hz, size_t *out_bytes) {
uint32_t num_samples = (uint32_t)(SAMPLE_RATE * duration_sec);
uint32_t data_size = num_samples * sizeof(int16_t);
size_t total_bytes = sizeof(WavHeader44) + data_size;
uint8_t *buffer = (uint8_t *)malloc(total_bytes);
if (!buffer) return NULL;
WavHeader44 hdr = {
.riff_id = {'R','I','F','F'}, .file_size = 36 + data_size,
.wave_id = {'W','A','V','E'}, .fmt_id = {'f','m','t',' '},
.fmt_size = 16, .audio_format = 1, .num_channels = 1,
.sample_rate = SAMPLE_RATE, .byte_rate = SAMPLE_RATE * 2,
.block_align = 2, .bits_per_sample = 16,
.data_id = {'d','a','t','a'}, .data_size = data_size
};
memcpy(buffer, &hdr, sizeof(WavHeader44));
int16_t *pcm = (int16_t *)(buffer + sizeof(WavHeader44));
float phase = 0.0f;
for (uint32_t i = 0; i < num_samples; ++i) {
float t = (float)i / (float)num_samples; // Normalized progress (0.0 ~ 1.0)
float freq = start_hz + (end_hz - start_hz) * t;
float env = expf(-5.0f * t); // Exponential decay volume envelope
phase += (TWO_PI * freq) / (float)SAMPLE_RATE;
if (phase >= TWO_PI) phase -= TWO_PI;
float sample_f = sinf(phase) * env;
pcm[i] = (int16_t)(sample_f * 28000.0f); // Headroom to prevent 16-bit clipping
}
if (out_bytes) *out_bytes = total_bytes;
return buffer; // Can be passed directly to raylib LoadWaveFromMemory(".wav", buffer, total_bytes)
}
2. 파형(Waveform) 수식 및 게임 효과음 매핑2. Waveform Formulas and Game Sound Effect Mapping
| 파형 종류 | 시간/위상 수식 ($\phi \in [0, 2\pi)$) | 음색 특징 및 주요 효과음 용도 |
|---|---|---|
| Sine (사인파) | $y = \sin(\phi)$ | 배음이 없는 순음; 코인 획득, UI 알림음, 서브 베이스 타격음 |
| Square (구형파) | $y = \sin(\phi) \ge 0 \; ? \; 1.0 : -1.0$ | 홀수 배음이 풍부한 8비트 음색; 점프, 레이저 발사, 레트로 메뉴 이동 |
| Sawtooth (톱니파) | $y = \frac{\phi}{\pi} - 1.0$ | 짝수·홀수 배음이 모두 포함된 날카로운 음색; 엔진 배기음, 경고 알람 |
| LFSR White Noise | lfsr ^= (lfsr >> 7); lfsr ^= (lfsr << 9); | 비주기적 광대역 스펙트럼; 폭발음, 충돌 파열음, 하이햇 타악기 |
| Waveform Type | Time / Phase Formula ($\phi \in [0, 2\pi)$) | Timbre Characteristics and SFX Use Cases |
|---|---|---|
| Sine Wave | $y = \sin(\phi)$ | Pure fundamental tone without harmonics; coin pickups, UI chimes, sub-bass impacts |
| Square Wave | $y = \sin(\phi) \ge 0 \; ? \; 1.0 : -1.0$ | Rich odd harmonics with 8-bit chiptune character; jumps, laser shots, retro menu blips |
| Sawtooth Wave | $y = \frac{\phi}{\pi} - 1.0$ | Contains both even and odd harmonics for a sharp buzz; engine rumble, warning alarms |
| LFSR White Noise | lfsr ^= (lfsr >> 7); lfsr ^= (lfsr << 9); | Aperiodic broadband spectrum; explosions, impact bursts, hi-hat percussion |
3. 실무 적용 및 주의사항3. Practical Considerations and Caveats
- 위상 연속성(Phase Continuity) 보장: 주파수가 시간에 따라 변하는 스윕(Sweep) 효과음을 만들 때
sinf(TWO_PI * freq * t)처럼 절대 시간t를 직접 곱하면 위상이 급격히 튀면서 찢어지는 잡음이 납니다. 반드시 샘플마다phase += (TWO_PI * freq) / SAMPLE_RATE로 위상을 누적해야 합니다. - 구조체 패딩 방지(
#pragma pack): 44바이트 WAV 헤더는 바이트 오프셋이 엄격히 고정된 바이너리 포맷이므로, 컴파일러가 임의로 정렬 패딩을 넣지 않도록#pragma pack(push, 1)을 지정하고sizeof(WavHeader44) == 44인지 확인해야 합니다. - 16비트 정수 오버플로우(Clipping) 방지:
int16_t의 표현 범위는-32768 ~ 32767입니다. 여러 파형을 합성할 때 합산 진폭이32767을 넘으면 정수 오버플로우로 인해 극심한 디지털 파열음이 발생하므로 스케일 상수를28000.0f수준으로 여유 있게 제한하거나 클램핑을 거칩니다.
- Preserve Phase Continuity: Evaluating
sinf(TWO_PI * freq * t)directly whenfreqchanges over time causes sudden phase jumps and audible clicking. Always integrate phase per sample viaphase += (TWO_PI * freq) / SAMPLE_RATE. - Prevent Struct Padding (
#pragma pack): Because the 44-byte WAV header is a fixed binary layout, wrap the struct definition in#pragma pack(push, 1)to prevent compiler alignment padding and verifysizeof(WavHeader44) == 44. - Avoid 16-Bit Integer Overflow (Clipping): The range of
int16_tis-32768 to 32767. Exceeding32767wraps around and produces harsh digital distortion, so scale normalized samples by around28000.0for clamp the output.
C 언어 수준에서 직접 PCM 파형을 합성하고 메모리 내 WAV 바이너리로 직렬화하면, 외부 에셋 파일 누락 걱정 없이 수 킬로바이트의 코드만으로 다채로운 아케이드 사운드 시스템을 구축할 수 있습니다.
Synthesizing PCM waveforms in C and serializing them into in-memory WAV buffers enables self-contained arcade audio systems in a few kilobytes of code without external asset files.