개요
학교 C언어 강의 과제로 바이브 코딩으로 1000줄(1인 작업 시 800줄) 콘솔 프로그램 코딩을 하게 되었습니다.
구현한 기능은 다음과 같습니다.
- 한 번에 1개 또는 2개 이상의 16bit PCM 형식의 .wav 파일을 읽고,
- 음역대를 지정하여 볼륨을 조정, 음역대 nomalize 후 새로운 .wav 파일 생성,
- .eqp 확장자로 텍스트 파일을 작성하여 프리셋 생성,
- .eqp 형태의 텍스트 파일을 읽고 프로그램에 적용.
주의점: 16bit PCM 형식의 .wav 파일만 지원하고, 경로에 한글이 있으면 동작하지 않습니다.
콘솔 이미지와 기능 설명

콘솔 화면과 함께 기능을 좀 더 자세히 설명하자면,
- Batch mode, 즉 한 번에 여러 개의 파일을 읽을지, 하나만 읽을지에 대한 질문을 하고 선택합니다.
- .eqp 프리셋 파일을 읽고 적용할 것인지, 직접 수치를 입력하여 이퀄라이징 할지를 선택합니다.
- 이퀄라이징 할 음역대의 개수를 선택합니다. default로 만들어진 음역대는 3개이므로 3개를 선택했습니다.
- 1은 저음역대(Bass line), 2를 입력하면 중간 음역대, 3은 고음역대를 조정합니다. 각각 default 값으로 120Hz, 1000Hz, 8000Hz가 선택되어 있습니다.
- 조정할 dB 값을 입력합니다. 저는 베이스를 좋아하기에, 저음역대만 증폭하고 나머지를 조금씩 줄여주었습니다.
- Q 값은 설정한 음역대에서 얼마나 위아래로 큰 음역대를 조정할지를 결정하는 값입니다. Q 값이 클수록 좁은 범위의 소리에 영향을 미칩니다. 보통 default 값으로 사용합니다.
- 마지막으로 nomalization 여부를 선택합니다. 볼륨을 음역대 별로 변경했으니, 전체 볼륨 중 가장 큰 부분을 기준으로 전체 볼륨을 조정합니다.
- dBFS는 decibel full-scale의 약어이며, 최종 볼륨 조정 목표치입니다. 0이 최댓값이고, default는 -1입니다. 작게 목표 dBFS를 잡을수록 결과물의 전체 볼륨도 작아지게 됩니다.
- 마지막으로 input과 output 파일의 경로를 입력하고 이퀄라이징을 실행합니다.

- output 파일이 경로에 생성됩니다.
- 현재 선택한 이퀄라이징 값들을 .eqp 프리셋 파일로 생성할지를 선택합니다.
- 프리셋 파일을 생성할 경로를 선택합니다.
사용한 AI와 프롬프트
저는 chatGPT 5를 사용하였고, 제가 사용한 프롬프트는 다음과 같습니다.
Q. 오디오 이퀄라이저를 만들고자 하는데, ANSI C로 가능해?
A. 가능하다, 예시 코드를 보여줄까?
Q. 저음 부스트만 가능한 버전으로 부탁해.
A. ~예시 코드~
Q. 네가 짜준 코드의 ~~ 부분에서 오류가 발생해.
A. ~수정된 교체용 함수~
반복...
Q. 이번엔 여러 밴드를 지원하는 버전의 코드를 보여줘.
A. ~예시 코드~
Q. 정규화와 배치 처리 추가를 부탁해.
A. ~수정 코드~
Q. 프리셋 파일을 생성하고 읽을 수 있으면 좋겠어.
A. ~삽입용 함수 코드~
완성.
후기
- 완성된 코드는 주석 포함 804줄이며, 저는 함수를 조립하고, printf로 지원하는 파일 형식과, GPT5를 이용하여 코딩했다는 것을 밝혀주는 부분만 직접 작성하였습니다.
- AI로는 학부 과제나 학습으로의 이용이 거의 대부분이었는데, 이번에 막상 바이브 코딩을 해보니 결과는 정말 놀라웠습니다. 800줄 정도의 짧은 코드여서인지, 코드가 굉장히 깔끔했습니다.
- 앞으로 조금만 더 발전하면, 즉 AI의 결함이 더 보완되면, 더욱 완성도 높고 길고 복잡한 코드를 프롬프트 입력만으로 짤 수 있는 날이 머지않았구나 하는 생각이 들었습니다.
- 미래의 코딩은 기본 문법과 코드를 읽을 줄 아는 능력, 즉 AI의 코드를 분석하고 결함을 수정하는 능력이 중요해질 것 같습니다.
- 앞으로는 더 효율적인 코드를 짜는 법 보다도 프롬프트를 잘 쓰는, AI를 잘 다루는 능력이 중요해질 것 같습니다.
- 다만 아쉬운 점은 인간이 생각하고 연구하지 않으니, 새롭게 발견되는 알고리즘이라거나 하는 혁신이 발생할 확률은 줄어들 것 같습니다. AI 시대라고 해서 너무 의존하지 않고 끝없이 사유하는 자세가 필요해 보입니다.
/* eq_multiband_batch.c
* ANSI C (C89). Multi-band EQ (Low/Peak/High) + Peak Normalization + Batch processing.
* Build: gcc eq_multiband_batch.c -o eq_multiband_batch -lm
* I/O: 16-bit PCM WAV (mono/stereo). Console-driven interactive program.
*/
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
/* --------- typedefs --------- */
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef short s16;
/* --------- WAV structures --------- */
typedef struct {
u16 audio_format; /* 1 = PCM */
u16 num_channels; /* 1 or 2 */
u32 sample_rate; /* e.g., 44100 */
u16 bits_per_sample; /* 16 */
u32 data_offset; /* offset to data chunk */
u32 data_size; /* bytes of audio data */
} WavMeta;
typedef struct {
WavMeta meta;
s16* interleaved; /* frames * channels */
u32 frames; /* samples per channel */
} AudioBuffer;
/* --------- helpers --------- */
static void chomp(char* s) {
size_t n;
if (!s) return;
n = strlen(s);
if (n && (s[n - 1] == '\n' || s[n - 1] == '\r')) s[n - 1] = '\0';
}
static int read_u32_le(FILE* f, u32* out) {
u8 b[4];
if (fread(b, 1, 4, f) != 4) return -1;
*out = (u32)b[0] | ((u32)b[1] << 8) | ((u32)b[2] << 16) | ((u32)b[3] << 24);
return 0;
}
static int read_u16_le(FILE* f, u16* out) {
u8 b[2];
if (fread(b, 1, 2, f) != 2) return -1;
*out = (u16)b[0] | ((u16)b[1] << 8);
return 0;
}
/* return pointer to last directory separator if any (or NULL) */
static const char* last_sep(const char* p) {
const char* s1, * s2;
s1 = strrchr(p, '/'); s2 = strrchr(p, '\\');
return (s1 && s2) ? (s1 > s2 ? s1 : s2) : (s1 ? s1 : s2);
}
/* build output path: insert suffix before extension (if ".wav" / ".WAV"), else append suffix + ".wav" */
static void build_out_path(const char* in_path, const char* suffix, char* out_path, size_t out_cap) {
const char* dot;
size_t len, base_len, suf_len;
if (!in_path || !out_path || out_cap == 0) return;
len = strlen(in_path);
if (len + 1 > out_cap) len = out_cap - 1;
strncpy(out_path, in_path, len);
out_path[len] = '\0';
/* find extension after last separator */
dot = strrchr(out_path, '.');
if (dot) {
const char* sep = last_sep(out_path);
if (sep && dot < sep) dot = NULL; /* dot was in directory name */
}
suf_len = (suffix ? strlen(suffix) : 0);
if (dot && (strcmp(dot, ".wav") == 0 || strcmp(dot, ".WAV") == 0)) {
base_len = (size_t)(dot - out_path);
if (base_len + suf_len + 4 + 1 > out_cap) suf_len = 0; /* avoid overflow; drop suffix if needed */
out_path[base_len] = '\0';
if (suffix) strcat(out_path, suffix);
strcat(out_path, ".wav");
}
else {
/* no .wav ext - just append suffix and .wav */
if (len + suf_len + 4 + 1 <= out_cap) {
if (suffix) strcat(out_path, suffix);
strcat(out_path, ".wav");
}
}
}
/* --------- WAV I/O (16-bit PCM only) --------- */
static int wav_read(const char* path, AudioBuffer* out) {
FILE* f;
u8 id[4];
u32 chunk_size = 0, data_size = 0, data_offset = 0;
u16 audio_format = 0, num_channels = 0, bits = 0;
u32 sample_rate = 0, byte_rate = 0;
u16 block_align = 0;
int found_fmt = 0, found_data = 0;
if (!path || !out) return -1;
f = fopen(path, "rb");
if (!f) {
perror("fopen");
fprintf(stderr, "open failed: %s\n", path);
return -1;
}
if (fread(id, 1, 4, f) != 4 || memcmp(id, "RIFF", 4) != 0) { fclose(f); return -1; }
if (read_u32_le(f, &chunk_size) != 0) { fclose(f); return -1; }
if (fread(id, 1, 4, f) != 4 || memcmp(id, "WAVE", 4) != 0) { fclose(f); return -1; }
while (fread(id, 1, 4, f) == 4) {
if (read_u32_le(f, &chunk_size) != 0) { fclose(f); return -1; }
if (memcmp(id, "fmt ", 4) == 0) {
u32 fmt_size = chunk_size;
if (read_u16_le(f, &audio_format) != 0) { fclose(f); return -1; }
if (read_u16_le(f, &num_channels) != 0) { fclose(f); return -1; }
if (read_u32_le(f, &sample_rate) != 0) { fclose(f); return -1; }
if (read_u32_le(f, &byte_rate) != 0) { fclose(f); return -1; }
if (read_u16_le(f, &block_align) != 0) { fclose(f); return -1; }
if (read_u16_le(f, &bits) != 0) { fclose(f); return -1; }
if (fmt_size > 16) fseek(f, fmt_size - 16, SEEK_CUR);
found_fmt = 1;
}
else if (memcmp(id, "data", 4) == 0) {
data_size = chunk_size;
data_offset = (u32)ftell(f);
fseek(f, data_size, SEEK_CUR);
found_data = 1;
}
else {
fseek(f, chunk_size, SEEK_CUR);
}
if (found_fmt && found_data) break;
}
if (!found_fmt || !found_data) { fclose(f); return -1; }
if (audio_format != 1 || bits != 16 || (num_channels != 1 && num_channels != 2)) {
fprintf(stderr, "Unsupported WAV (need PCM 16-bit mono/stereo)\n"); fclose(f); return -1;
}
out->meta.audio_format = audio_format;
out->meta.num_channels = num_channels;
out->meta.sample_rate = sample_rate;
out->meta.bits_per_sample = bits;
out->meta.data_offset = data_offset;
out->meta.data_size = data_size;
out->frames = data_size / (num_channels * 2);
out->interleaved = (s16*)malloc(data_size);
if (!out->interleaved) { fclose(f); return -1; }
fseek(f, data_offset, SEEK_SET);
if (fread(out->interleaved, 1, data_size, f) != data_size) {
free(out->interleaved); fclose(f); return -1;
}
fclose(f);
return 0;
}
static int wav_write(const char* path, const AudioBuffer* ab) {
FILE* f;
u32 data_size, riff_size, byte_rate;
u16 block_align, bps;
if (!path || !ab) return -1;
f = fopen(path, "wb");
if (!f) { perror("fopen"); fprintf(stderr, "write open failed: %s\n", path); return -1; }
data_size = ab->frames * ab->meta.num_channels * 2;
riff_size = 36 + data_size;
block_align = (u16)(ab->meta.num_channels * 2);
byte_rate = ab->meta.sample_rate * block_align;
bps = 16;
fwrite("RIFF", 1, 4, f);
fwrite(&riff_size, 4, 1, f);
fwrite("WAVE", 1, 4, f);
fwrite("fmt ", 1, 4, f);
{ u32 fmt_size = 16; fwrite(&fmt_size, 4, 1, f); }
{ u16 af = 1; fwrite(&af, 2, 1, f); }
fwrite(&ab->meta.num_channels, 2, 1, f);
fwrite(&ab->meta.sample_rate, 4, 1, f);
fwrite(&byte_rate, 4, 1, f);
fwrite(&block_align, 2, 1, f);
fwrite(&bps, 2, 1, f);
fwrite("data", 1, 4, f);
fwrite(&data_size, 4, 1, f);
if (fwrite(ab->interleaved, 1, data_size, f) != data_size) { fclose(f); return -1; }
fclose(f);
return 0;
}
static void wav_free(AudioBuffer* ab) {
if (ab && ab->interleaved) { free(ab->interleaved); ab->interleaved = NULL; }
}
/* --------- EQ / Biquad --------- */
typedef struct {
double b0, b1, b2, a0, a1, a2;
double z1L, z2L, z1R, z2R; /* per-channel state */
} Biquad;
static int biquad_design_lowshelf(Biquad* bq, double fs, double fc, double gain_db, double q) {
double A, w0, cw, sw, alpha, sqrtA;
double b0, b1, b2, a0, a1, a2;
if (!bq || fs <= 0.0 || fc <= 0.0 || q <= 0.0) return -1;
A = pow(10.0, gain_db / 40.0);
w0 = 2.0 * 3.14159265358979323846 * fc / fs;
cw = cos(w0); sw = sin(w0);
alpha = sw / (2.0 * q);
sqrtA = sqrt(A);
b0 = A * ((A + 1.0) - (A - 1.0) * cw + 2.0 * sqrtA * alpha);
b1 = 2 * A * ((A - 1.0) - (A + 1.0) * cw);
b2 = A * ((A + 1.0) - (A - 1.0) * cw - 2.0 * sqrtA * alpha);
a0 = (A + 1.0) + (A - 1.0) * cw + 2.0 * sqrtA * alpha;
a1 = -2 * ((A - 1.0) + (A + 1.0) * cw);
a2 = (A + 1.0) + (A - 1.0) * cw - 2.0 * sqrtA * alpha;
if (a0 == 0.0) return -1;
bq->b0 = b0 / a0; bq->b1 = b1 / a0; bq->b2 = b2 / a0; bq->a0 = 1.0; bq->a1 = a1 / a0; bq->a2 = a2 / a0;
bq->z1L = bq->z2L = bq->z1R = bq->z2R = 0.0; return 0;
}
static int biquad_design_highshelf(Biquad* bq, double fs, double fc, double gain_db, double q) {
double A, w0, cw, sw, alpha, sqrtA;
double b0, b1, b2, a0, a1, a2;
if (!bq || fs <= 0.0 || fc <= 0.0 || q <= 0.0) return -1;
A = pow(10.0, gain_db / 40.0);
w0 = 2.0 * 3.14159265358979323846 * fc / fs;
cw = cos(w0); sw = sin(w0);
alpha = sw / (2.0 * q);
sqrtA = sqrt(A);
b0 = A * ((A + 1.0) + (A - 1.0) * cw + 2.0 * sqrtA * alpha);
b1 = -2 * A * ((A - 1.0) + (A + 1.0) * cw);
b2 = A * ((A + 1.0) + (A - 1.0) * cw - 2.0 * sqrtA * alpha);
a0 = (A + 1.0) - (A - 1.0) * cw + 2.0 * sqrtA * alpha;
a1 = 2 * ((A - 1.0) - (A + 1.0) * cw);
a2 = (A + 1.0) - (A - 1.0) * cw - 2.0 * sqrtA * alpha;
if (a0 == 0.0) return -1;
bq->b0 = b0 / a0; bq->b1 = b1 / a0; bq->b2 = b2 / a0; bq->a0 = 1.0; bq->a1 = a1 / a0; bq->a2 = a2 / a0;
bq->z1L = bq->z2L = bq->z1R = bq->z2R = 0.0; return 0;
}
static int biquad_design_peaking(Biquad* bq, double fs, double fc, double gain_db, double q) {
double A, w0, cw, sw, alpha;
double b0, b1, b2, a0, a1, a2;
if (!bq || fs <= 0.0 || fc <= 0.0 || q <= 0.0) return -1;
A = pow(10.0, gain_db / 40.0);
w0 = 2.0 * 3.14159265358979323846 * fc / fs;
cw = cos(w0); sw = sin(w0);
alpha = sw / (2.0 * q);
b0 = 1.0 + alpha * A;
b1 = -2.0 * cw;
b2 = 1.0 - alpha * A;
a0 = 1.0 + alpha / A;
a1 = -2.0 * cw;
a2 = 1.0 - alpha / A;
if (a0 == 0.0) return -1;
bq->b0 = b0 / a0; bq->b1 = b1 / a0; bq->b2 = b2 / a0; bq->a0 = 1.0; bq->a1 = a1 / a0; bq->a2 = a2 / a0;
bq->z1L = bq->z2L = bq->z1R = bq->z2R = 0.0; return 0;
}
static double biquad_process_sample(double x, double* z1, double* z2, const Biquad* bq) {
double y, nz1, nz2;
y = bq->b0 * x + *z1;
nz1 = bq->b1 * x - bq->a1 * y + *z2;
nz2 = bq->b2 * x - bq->a2 * y;
*z1 = nz1; *z2 = nz2;
return y;
}
/* --------- EQ chain --------- */
#define MAX_BANDS 16
typedef enum {
BAND_LOW_SHELF = 1,
BAND_PEAKING = 2,
BAND_HIGH_SHELF = 3
} BandType;
typedef struct {
BandType type;
double fc;
double gain_db;
double q;
Biquad biq; /* designed per band */
} EqBand;
typedef struct {
EqBand bands[MAX_BANDS];
int count;
} EqChain;
static int eq_design_chain(EqChain* chain, double fs) {
int i, ok;
if (!chain || fs <= 0.0) return -1;
for (i = 0; i < chain->count; ++i) {
if (chain->bands[i].type == BAND_LOW_SHELF)
ok = biquad_design_lowshelf(&chain->bands[i].biq, fs, chain->bands[i].fc, chain->bands[i].gain_db, chain->bands[i].q);
else if (chain->bands[i].type == BAND_HIGH_SHELF)
ok = biquad_design_highshelf(&chain->bands[i].biq, fs, chain->bands[i].fc, chain->bands[i].gain_db, chain->bands[i].q);
else
ok = biquad_design_peaking(&chain->bands[i].biq, fs, chain->bands[i].fc, chain->bands[i].gain_db, chain->bands[i].q);
if (ok != 0) return -1;
}
return 0;
}
static int eq_process_chain(AudioBuffer* ab, EqChain* chain) {
u32 i;
int idx, b, chs;
double scale, inL, inR, outL, outR, yL, yR;
Biquad* bq;
if (!ab || !chain) return -1;
chs = ab->meta.num_channels;
scale = 1.0 / 32768.0;
for (i = 0; i < ab->frames; ++i) {
idx = (int)(i * chs);
inL = (double)ab->interleaved[idx] * scale;
if (chs == 2) inR = (double)ab->interleaved[idx + 1] * scale; else inR = inL;
outL = inL; outR = inR;
for (b = 0; b < chain->count; ++b) {
bq = &chain->bands[b].biq; /* update state in-place */
yL = biquad_process_sample(outL, &bq->z1L, &bq->z2L, bq);
yR = biquad_process_sample(outR, &bq->z1R, &bq->z2R, bq);
outL = yL; outR = yR;
}
if (outL > 0.999969) outL = 0.999969; else if (outL < -1.0) outL = -1.0;
if (outR > 0.999969) outR = 0.999969; else if (outR < -1.0) outR = -1.0;
ab->interleaved[idx] = (s16)(outL * 32767.0);
if (chs == 2) ab->interleaved[idx + 1] = (s16)(outR * 32767.0);
}
return 0;
}
/* --------- Peak normalization (to target dBFS) --------- */
static double measure_peak_amplitude(const AudioBuffer* ab) {
u32 i, frames;
int chs, idx;
double scale, maxa, v;
if (!ab || !ab->interleaved) return 0.0;
chs = ab->meta.num_channels;
frames = ab->frames;
scale = 1.0 / 32768.0;
maxa = 0.0;
for (i = 0; i < frames; ++i) {
idx = (int)(i * chs);
v = fabs((double)ab->interleaved[idx] * scale);
if (v > maxa) maxa = v;
if (chs == 2) {
v = fabs((double)ab->interleaved[idx + 1] * scale);
if (v > maxa) maxa = v;
}
}
return maxa; /* in [0,1] */
}
static void normalize_to_target_dbfs(AudioBuffer* ab, double target_dbfs) {
double peak, target_amp, scale;
u32 i, frames;
int chs, idx;
double vL, vR, outL, outR;
if (!ab || !ab->interleaved) return;
peak = measure_peak_amplitude(ab); /* [0..1] */
if (peak <= 0.0) return;
/* dBFS to linear amplitude: amp = 10^(dB/20) */
target_amp = pow(10.0, target_dbfs / 20.0); /* e.g., -1.0 dBFS -> 0.891... */
if (target_amp <= 0.0) return;
scale = target_amp / peak;
frames = ab->frames;
chs = ab->meta.num_channels;
for (i = 0; i < frames; ++i) {
idx = (int)(i * chs);
vL = (double)ab->interleaved[idx] / 32767.0;
outL = vL * scale;
if (outL > 0.999969) outL = 0.999969; else if (outL < -1.0) outL = -1.0;
ab->interleaved[idx] = (s16)(outL * 32767.0);
if (chs == 2) {
vR = (double)ab->interleaved[idx + 1] / 32767.0;
outR = vR * scale;
if (outR > 0.999969) outR = 0.999969; else if (outR < -1.0) outR = -1.0;
ab->interleaved[idx + 1] = (s16)(outR * 32767.0);
}
}
}
static void str_trim(char* s) {
char* p; size_t n;
if (!s) return;
/* rtrim */
n = strlen(s);
while (n > 0 && (s[n - 1] == '\n' || s[n - 1] == '\r' || isspace((unsigned char)s[n - 1]))) { s[n - 1] = '\0'; --n; }
/* ltrim */
p = s;
while (*p && isspace((unsigned char)*p)) ++p;
if (p != s) memmove(s, p, strlen(p) + 1);
}
static void str_tolower(char* s) {
size_t i, n;
if (!s) return;
n = strlen(s);
for (i = 0; i < n; ++i) s[i] = (char)tolower((unsigned char)s[i]);
}
static int parse_band_type(const char* t, BandType* out) {
char tmp[32];
size_t n;
if (!t || !out) return -1;
n = strlen(t); if (n > 31) n = 31;
memcpy(tmp, t, n); tmp[n] = '\0';
str_tolower(tmp);
if (strcmp(tmp, "lowshelf") == 0 || strcmp(tmp, "low") == 0 || strcmp(tmp, "ls") == 0) { *out = BAND_LOW_SHELF; return 0; }
if (strcmp(tmp, "highshelf") == 0 || strcmp(tmp, "high") == 0 || strcmp(tmp, "hs") == 0) { *out = BAND_HIGH_SHELF; return 0; }
if (strcmp(tmp, "peaking") == 0 || strcmp(tmp, "peak") == 0 || strcmp(tmp, "pk") == 0) { *out = BAND_PEAKING; return 0; }
return -1;
}
/* band line format: bandN=type,fc,gain,Q=val OR bandN=type,fc,gain,q
examples:
band0=peaking,1000,3.0,Q=1.0
band1=lowshelf,120,6.0,0.707
*/
static int parse_band_line(const char* value, EqBand* out) {
char buf[256], * p, * tok;
char* parts[4]; int pc = 0;
BandType bt;
double fc = 0.0, gain = 0.0, q = 0.707;
if (!value || !out) return -1;
/* copy and strip spaces */
strncpy(buf, value, sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\0';
str_trim(buf);
/* split by comma up to 4 tokens */
p = buf;
while ((tok = strtok(pc == 0 ? p : NULL, ",")) != NULL && pc < 4) {
str_trim(tok);
parts[pc++] = tok;
}
if (pc < 3) return -1;
/* type */
if (parse_band_type(parts[0], &bt) != 0) return -1;
/* fc */
if (sscanf(parts[1], "%lf", &fc) != 1 || fc <= 0.0) return -1;
/* gain */
if (sscanf(parts[2], "%lf", &gain) != 1) return -1;
/* Q */
if (pc >= 4) {
/* accept "Q=1.0" or just "1.0" */
if (parts[3][0] == 'Q' || parts[3][0] == 'q') {
char* eq = strchr(parts[3], '=');
if (!eq) return -1;
if (sscanf(eq + 1, "%lf", &q) != 1 || q <= 0.0) return -1;
}
else {
if (sscanf(parts[3], "%lf", &q) != 1 || q <= 0.0) return -1;
}
}
out->type = bt;
out->fc = fc;
out->gain_db = gain;
out->q = q;
/* biquad state will be zeroed by design step later */
memset(&out->biq, 0, sizeof(out->biq));
return 0;
}
/* save: text file
format:
name=PresetName
bands=N
band0=peaking,1000,3.0,Q=1.0
band1=lowshelf,120,6.0,Q=0.707
*/
int preset_save(const char* path, const EqChain* chain, const char* name) {
FILE* f;
int i;
const char* tname;
if (!path || !chain) return -1;
f = fopen(path, "wb");
if (!f) { perror("fopen"); return -1; }
fprintf(f, "# EQ preset file\n");
if (name && name[0]) fprintf(f, "name=%s\n", name);
fprintf(f, "bands=%d\n", chain->count);
for (i = 0; i < chain->count; ++i) {
switch (chain->bands[i].type) {
case BAND_LOW_SHELF: tname = "lowshelf"; break;
case BAND_HIGH_SHELF: tname = "highshelf"; break;
default: tname = "peaking"; break;
}
fprintf(f, "band%d=%s,%.6g,%.6g,Q=%.6g\n", i, tname,
chain->bands[i].fc, chain->bands[i].gain_db, chain->bands[i].q);
}
fclose(f);
return 0;
}
int preset_load(const char* path, EqChain* out_chain) {
FILE* f;
char line[512];
int bands_decl = -1;
int assigned = 0;
if (!path || !out_chain) return -1;
memset(out_chain, 0, sizeof(*out_chain));
f = fopen(path, "rb");
if (!f) { perror("fopen"); return -1; }
while (fgets(line, sizeof(line), f)) {
char* eq, * key, * val;
str_trim(line);
if (line[0] == '\0') continue;
if (line[0] == '#' || line[0] == ';') continue;
eq = strchr(line, '=');
if (!eq) continue;
*eq = '\0';
key = line;
val = eq + 1;
str_trim(key);
str_trim(val);
if (strcmp(key, "bands") == 0) {
int n = atoi(val);
if (n < 0) n = 0;
if (n > MAX_BANDS) n = MAX_BANDS;
bands_decl = n;
out_chain->count = n;
}
else if (strncmp(key, "band", 4) == 0) {
int idx = -1;
if (sscanf(key + 4, "%d", &idx) == 1 && idx >= 0 && idx < MAX_BANDS) {
EqBand tmp;
if (parse_band_line(val, &tmp) == 0) {
out_chain->bands[idx] = tmp;
if (idx + 1 > out_chain->count) out_chain->count = idx + 1;
++assigned;
}
}
}
else {
/* ignore name or unknown keys */
}
}
fclose(f);
/* If bands count not declared, infer from highest index assigned. */
if (bands_decl < 0) {
/* out_chain->count already reflects max index+1 */
}
return (out_chain->count > 0) ? 0 : -1;
}
/* --------- interactive UI --------- */
#define MAX_FILES 256
int main(void) {
char line[256];
char in_path[1024], out_path[1024], suffix[64], save_path[1024], namebuf[128], preset_path[1024];
int batch, i, nfiles;
int nbands, b;
EqChain chain;
int do_norm;
double target_dbfs;
int use_preset = 0;
int save_preset = 0;
printf("== Multi-band WAV EQ + Normalize + Batch (ANSI C) ==\n");
printf("Only WAV files in 16-bit PCM format(44.1kHz, mono/stereo) are supported.\n");
printf("--------------------------powered by chatGPT 5---------------------------\n");
/* Batch or Single */
printf("Batch mode? 0=Single file, 1=Batch: ");
if (!fgets(line, sizeof(line), stdin)) return 1;
batch = atoi(line);
if (batch != 0 && batch != 1) batch = 0;
printf("Load preset from file? 0=No, 1=Yes: ");
if (fgets(line, sizeof(line), stdin)) use_preset = atoi(line);
if (use_preset == 1) {
EqChain loaded;
printf("Preset file path: ");
if (!fgets(preset_path, sizeof(preset_path), stdin)) return 1;
chomp(preset_path);
if (preset_load(preset_path, &loaded) != 0) {
fprintf(stderr, "Failed to load preset.\n");
return 1;
}
chain = loaded; /* chain은 기존에 쓰는 EqChain 변수 */
printf("Loaded %d band(s) from preset.\n", chain.count);
/* 이후 eq_design_chain(...) 으로 바로 진행 */
}
else {
/* EQ bands setup (same for single and batch) */
memset(&chain, 0, sizeof(chain));
printf("Number of EQ bands (1..%d): ", MAX_BANDS);
if (!fgets(line, sizeof(line), stdin)) return 1;
nbands = atoi(line);
if (nbands < 1) nbands = 1;
if (nbands > MAX_BANDS) nbands = MAX_BANDS;
chain.count = nbands;
for (b = 0; b < nbands; ++b) {
int t;
double fc, gain_db, q;
printf("\nBand #%d:\n", b + 1);
printf(" Type (1=LowShelf, 2=Peaking, 3=HighShelf): ");
if (!fgets(line, sizeof(line), stdin)) return 1;
t = atoi(line);
if (t < 1 || t > 3) t = 2;
fc = (t == 1) ? 120.0 : ((t == 3) ? 8000.0 : 1000.0);
gain_db = 0.0;
q = (t == 2) ? 1.0 : 0.707;
printf(" fc (Hz) [default %.1f]: ", fc);
if (fgets(line, sizeof(line), stdin)) { double tmp; if (sscanf(line, "%lf", &tmp) == 1 && tmp > 0.0) fc = tmp; }
printf(" gain (dB) [default %.1f]: ", gain_db);
if (fgets(line, sizeof(line), stdin)) { double tmp; if (sscanf(line, "%lf", &tmp) == 1) gain_db = tmp; }
printf(" Q [default %.3f]: ", q);
if (fgets(line, sizeof(line), stdin)) { double tmp; if (sscanf(line, "%lf", &tmp) == 1 && tmp > 0.0) q = tmp; }
chain.bands[b].type = (BandType)t;
chain.bands[b].fc = fc;
chain.bands[b].gain_db = gain_db;
chain.bands[b].q = q;
}
}
/* Normalization option */
do_norm = 0;
target_dbfs = -1.0;
printf("\nNormalize output? 0=No, 1=Yes [default target -1.0 dBFS]: ");
if (fgets(line, sizeof(line), stdin)) {
int tnorm = atoi(line);
if (tnorm == 1) {
do_norm = 1;
printf(" Target dBFS (negative, e.g., -1.0): ");
if (fgets(line, sizeof(line), stdin)) {
double tmp;
if (sscanf(line, "%lf", &tmp) == 1) target_dbfs = tmp;
}
}
}
if (!batch) {
/* Single file */
AudioBuffer ab;
int ok;
memset(&ab, 0, sizeof(ab));
printf("\nInput WAV path: ");
if (!fgets(in_path, sizeof(in_path), stdin)) return 1;
chomp(in_path);
printf("Output WAV path (leave empty to auto-name with suffix): ");
if (!fgets(out_path, sizeof(out_path), stdin)) return 1;
chomp(out_path);
if (out_path[0] == '\0') {
strcpy(suffix, "_eq");
build_out_path(in_path, suffix, out_path, sizeof(out_path));
}
printf("\nReading: %s\n", in_path);
ok = wav_read(in_path, &ab);
if (ok != 0) { fprintf(stderr, "WAV read error.\n"); return 1; }
printf("SampleRate=%u Hz, Channels=%u, Frames=%u\n",
(unsigned)ab.meta.sample_rate, (unsigned)ab.meta.num_channels, (unsigned)ab.frames);
if (eq_design_chain(&chain, (double)ab.meta.sample_rate) != 0) {
fprintf(stderr, "EQ design failed.\n"); wav_free(&ab); return 1;
}
printf("Processing EQ...\n");
if (eq_process_chain(&ab, &chain) != 0) { wav_free(&ab); return 1; }
if (do_norm) {
printf("Normalizing to %.2f dBFS...\n", target_dbfs);
normalize_to_target_dbfs(&ab, target_dbfs);
}
printf("Writing: %s\n", out_path);
if (wav_write(out_path, &ab) != 0) { fprintf(stderr, "WAV write error.\n"); wav_free(&ab); return 1; }
wav_free(&ab);
printf("Done.\n");
}
else {
/* Batch mode */
char paths_in[MAX_FILES][1024];
char paths_out[MAX_FILES][1024];
char suf_local[64];
AudioBuffer ab2;
int ok2;
printf("\nHow many files? (1..%d): ", MAX_FILES);
if (!fgets(line, sizeof(line), stdin)) return 1;
nfiles = atoi(line);
if (nfiles < 1) nfiles = 1;
if (nfiles > MAX_FILES) nfiles = MAX_FILES;
for (i = 0; i < nfiles; ++i) {
printf("Input #%d path: ", i + 1);
if (!fgets(paths_in[i], sizeof(paths_in[i]), stdin)) return 1;
chomp(paths_in[i]);
}
printf("Output suffix to append before .wav (default _eq): ");
if (!fgets(suf_local, sizeof(suf_local), stdin)) return 1;
chomp(suf_local);
if (suf_local[0] == '\0') strcpy(suf_local, "_eq");
for (i = 0; i < nfiles; ++i) {
build_out_path(paths_in[i], suf_local, paths_out[i], sizeof(paths_out[i]));
}
for (i = 0; i < nfiles; ++i) {
memset(&ab2, 0, sizeof(ab2));
printf("\n[%d/%d] Reading: %s\n", i + 1, nfiles, paths_in[i]);
ok2 = wav_read(paths_in[i], &ab2);
if (ok2 != 0) { fprintf(stderr, " -> skip (read error)\n"); continue; }
printf(" SR=%u, CH=%u, FR=%u\n",
(unsigned)ab2.meta.sample_rate, (unsigned)ab2.meta.num_channels, (unsigned)ab2.frames);
if (eq_design_chain(&chain, (double)ab2.meta.sample_rate) != 0) {
fprintf(stderr, " -> skip (design error)\n"); wav_free(&ab2); continue;
}
printf(" Processing EQ...\n");
if (eq_process_chain(&ab2, &chain) != 0) { fprintf(stderr, " -> skip (process error)\n"); wav_free(&ab2); continue; }
if (do_norm) {
printf(" Normalizing to %.2f dBFS...\n", target_dbfs);
normalize_to_target_dbfs(&ab2, target_dbfs);
}
printf(" Writing: %s\n", paths_out[i]);
if (wav_write(paths_out[i], &ab2) != 0) { fprintf(stderr, " -> skip (write error)\n"); wav_free(&ab2); continue; }
wav_free(&ab2);
printf(" Done.\n");
}
printf("\nBatch finished.\n");
}
printf("Save current EQ as preset file? 0=No, 1=Yes: ");
if (fgets(line, sizeof(line), stdin)) save_preset = atoi(line);
if (save_preset == 1) {
printf("Preset name (optional): ");
if (!fgets(namebuf, sizeof(namebuf), stdin)) namebuf[0] = '\0';
chomp(namebuf);
printf("Output preset path (e.g., C:\\presets\\my.eqp): ");
if (!fgets(save_path, sizeof(save_path), stdin)) return 1;
chomp(save_path);
if (preset_save(save_path, &chain, namebuf[0] ? namebuf : NULL) != 0) {
fprintf(stderr, "Failed to save preset.\n");
}
else {
printf("Preset saved: %s\n", save_path);
}
}
#ifdef _MSC_VER
printf("Press Enter to exit...");
fgets(line, sizeof(line), stdin);
#endif
return 0;
}'코딩 프로젝트 > ANSI C' 카테고리의 다른 글
| [C언어기초] 12/02 Quiz Solution (0) | 2025.12.02 |
|---|---|
| [C언어기초] 11/25 Quiz Solution (0) | 2025.11.20 |
| [C언어기초] 11/11 Quiz Solution (0) | 2025.11.11 |
| [C언어기초] 11/4 Quiz Solution (0) | 2025.11.04 |