Ver3.0 ๐ต C++ ์ค๋์ค ํ๋ก๊ทธ๋๋ฐ๊ณผ ์ค์๊ฐ ์ฌ์ด๋ ์ฒ๋ฆฌ ๊ตฌํ: ์๋ฆฌ๋ฅผ ์ฝ๋๋ก ๋ค๋ฃจ๋ ๋ง๋ฒ ๊ฐ์ ์ฌ์

๐ต C++ ์ค๋์ค ํ๋ก๊ทธ๋๋ฐ๊ณผ ์ค์๊ฐ ์ฌ์ด๋ ์ฒ๋ฆฌ ๊ตฌํ: ์๋ฆฌ๋ฅผ ์ฝ๋๋ก ๋ค๋ฃจ๋ ๋ง๋ฒ ๊ฐ์ ์ฌ์
์์ ์คํธ๋ฆฌ๋ฐ๋ถํฐ ๊ฒ์ ์ฌ์ด๋๊น์ง, C++๋ก ๋ง๋๋ ์ค๋์ค์ ์ธ๊ณ ๐ง
์๋
! ํน์ ๊ฒ์์ ํ๋ค๊ฐ "์ด ์ฌ์ด๋ ํจ๊ณผ๋ ์ด๋ป๊ฒ ๋ง๋ค์ด์ง๋ ๊ฑฐ์ง?" ํ๊ณ ๊ถ๊ธํดํ ์ ์์ด? ๐ฎ ์๋๋ฉด ์์
์ฑ์์ ์ดํ๋ผ์ด์ ๋ฅผ ์กฐ์ ํ๋ฉด์ "์ด๊ฒ ๋ด๋ถ์ ์ผ๋ก ์ด๋ป๊ฒ ์๋ํ๋ ๊ฑฐ์ผ?" ํ๊ณ ์๊ฐํด๋ณธ ์ ์? ๋ฐ๋ก ๊ทธ ๋ต์ด ์ค๋ ์ฐ๋ฆฌ๊ฐ ๋ค๋ฃฐ C++ ์ค๋์ค ํ๋ก๊ทธ๋๋ฐ์ ์์ด!
์ค๋์ค ํ๋ก๊ทธ๋๋ฐ์ ๋จ์ํ ์๋ฆฌ ํ์ผ์ ์ฌ์ํ๋ ๊ฒ์ ๋์ด์, ์ค์๊ฐ์ผ๋ก ์ฌ์ด๋๋ฅผ ์์ฑํ๊ณ , ๋ณํํ๊ณ , ์ฒ๋ฆฌํ๋ ๊ธฐ์ ์ด์ผ. ๋ง์น ์๋ฆฌ์ฌ๊ฐ ์ฌ๋ฃ๋ฅผ ๋ค๋ฃจ๋ฏ์ด, ์ฐ๋ฆฌ๋ ์ฌ์ด๋ ์จ์ด๋ธ๋ฅผ ๋ค๋ฃจ๋ ๊ฑฐ์ง. ๊ทธ๋ฆฌ๊ณ C++๋ ์ด๋ฐ ์์
์ ํ๊ธฐ์ ์ต์ ์ ์ธ์ด์ผ. ์๋๊ณ ? ์๋๊ฐ ๋น ๋ฅด๊ณ , ํ๋์จ์ด์ ๊ฐ๊น์ด ์ ๊ทผํ ์ ์๊ณ , ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ธ๋ฐํ๊ฒ ์ ์ดํ ์ ์๊ฑฐ๋ ! ๐
๐ผ ์ค๋์ค ํ๋ก๊ทธ๋๋ฐ์ ๊ธฐ์ด: ์๋ฆฌ๋ ์ด๋ป๊ฒ ๋์งํธ์ด ๋ ๊น?
๋จผ์ ๊ธฐ๋ณธ๋ถํฐ ์์๋ณด์. ์๋ฆฌ๋ ๊ณต๊ธฐ์ ์ง๋์ด์ผ. ์ด ์ง๋์ ์ซ์๋ก ๋ฐ๊พธ๋ ๊ณผ์ ์ด ๋ฐ๋ก ๋์งํธ ์ค๋์ค์ ์์์ด์ง. ์ด ๊ณผ์ ์ ์ํ๋ง(Sampling)์ด๋ผ๊ณ ํด. ๐ฏ
์์ํด๋ด. ํ๋๊ฐ ์น๋ ๋ชจ์ต์ ์ฌ์ง์ผ๋ก ์ฐ๋๋ค๊ณ ์๊ฐํด๋ณด์. 1์ด์ ํ ์ฅ ์ฐ์ผ๋ฉด ํ๋์ ์์ง์์ ์ ๋๋ก ๋ด์ ์ ์๊ฒ ์ง? ํ์ง๋ง 1์ด์ 1000์ฅ์ ์ฐ์ผ๋ฉด ํจ์ฌ ๋ถ๋๋ฌ์ด ์์ง์์ ๊ธฐ๋กํ ์ ์์ด. ์ค๋์ค๋ ๋ง์ฐฌ๊ฐ์ง์ผ!
์ํ๋ ์ดํธ(Sample Rate): 1์ด์ ๋ช ๋ฒ ์๋ฆฌ๋ฅผ ์ธก์ ํ๋๊ฐ? CD ์์ง์ 44,100Hz (1์ด์ 44,100๋ฒ!)
๋นํธ ๋์ค(Bit Depth): ๊ฐ ์ํ์ ์ผ๋ง๋ ์ ๋ฐํ๊ฒ ๊ธฐ๋กํ๋๊ฐ? 16๋นํธ = 65,536๋จ๊ณ์ ์๋ ํํ
์ฑ๋(Channels): ๋ชจ๋ ธ(1์ฑ๋), ์คํ ๋ ์ค(2์ฑ๋), ์๋ผ์ด๋(5.1์ฑ๋ ๋ฑ)
๐ข ์ํ๊ณผ ํ๋ ์์ ๊ด๊ณ
C++๋ก ์ค๋์ค๋ฅผ ๋ค๋ฃฐ ๋ ๊ฐ์ฅ ๋จผ์ ์ดํดํด์ผ ํ ๊ฐ๋
์ด ๋ฐ๋ก ์ํ๊ณผ ํ๋ ์์ด์ผ. ์ํ์ ํ๋์ ์ฑ๋์์ ํ ์์ ์ ์๋ ๊ฐ์ด๊ณ , ํ๋ ์์ ๋ชจ๋ ์ฑ๋์ ์ํ์ ๋ฌถ์ ๊ฑฐ์ผ.
์๋ฅผ ๋ค์ด ์คํ
๋ ์ค ์ค๋์ค์์:
- 1 ํ๋ ์ = ์ผ์ชฝ ์ฑ๋ ์ํ 1๊ฐ + ์ค๋ฅธ์ชฝ ์ฑ๋ ์ํ 1๊ฐ
- 44,100Hz ์คํ
๋ ์ค = ์ด๋น 44,100 ํ๋ ์ = ์ด๋น 88,200 ์ํ
์ด๊ฒ ์ ์ค์ํ๋๊ณ ? ๋ฉ๋ชจ๋ฆฌ ๊ณ์ฐํ ๋ ํ์๊ฑฐ๋ ! ๐
// ์ค๋์ค ๋ฐ์ดํฐ ํฌ๊ธฐ ๊ณ์ฐ ์์
int sampleRate = 44100; // 44.1kHz
int channels = 2; // ์คํ
๋ ์ค
int bitDepth = 16; // 16๋นํธ
int duration = 10; // 10์ด
// ํ์ํ ๋ฉ๋ชจ๋ฆฌ ํฌ๊ธฐ (๋ฐ์ดํธ)
long long totalBytes = sampleRate * channels * (bitDepth / 8) * duration;
// 44100 * 2 * 2 * 10 = 1,764,000 ๋ฐ์ดํธ โ 1.68 MB
std::cout << "10์ด ์คํ
๋ ์ค ์ค๋์ค: " << totalBytes / 1024.0 / 1024.0 << " MB" << std::endl;
๐ ๏ธ C++ ์ค๋์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ํํ๊ธฐ
์, ์ด์ ์ค์ ๋ก ์ฝ๋๋ฅผ ์์ฑํ๋ ค๋ฉด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ํ์ํด. C++์๋ ์ ๋ง ๋ค์ํ ์ค๋์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์๋๋ฐ, ๊ฐ๊ฐ ์ฅ๋จ์ ์ด ์์ด. ๋ง์น ์๋ฆฌํ ๋ ์ด๋ค ๋๊ตฌ๋ฅผ ์ธ์ง ์ ํํ๋ ๊ฒ์ฒ๋ผ ๋ง์ด์ผ! ๐ณ
| ๋ผ์ด๋ธ๋ฌ๋ฆฌ | ํน์ง | ๋์ด๋ | ์ถ์ฒ ์ฉ๋ |
|---|---|---|---|
| PortAudio | ํฌ๋ก์ค ํ๋ซํผ, ์ ์์ค ์ ์ด | ์ค๊ธ | ์ค์๊ฐ ์ค๋์ค I/O |
| RtAudio | ๊ฐ๋จํ API, C++ ์นํ์ | ์ด๊ธ | ๋น ๋ฅธ ํ๋กํ ํ์ดํ |
| JUCE | ์์ ํ ํ๋ ์์ํฌ, GUI ํฌํจ | ์ค์๊ธ | ์ ๋ฌธ ์ค๋์ค ์ฑ |
| OpenAL | 3D ์ค๋์ค ํนํ | ์ค๊ธ | ๊ฒ์ ๊ฐ๋ฐ |
| SDL_mixer | ๊ฒ์ ์ค์ฌ, ๊ฐ๋จํจ | ์ด๊ธ | ์ธ๋ ๊ฒ์ |
๐ฏ PortAudio๋ก ์์ํ๊ธฐ
PortAudio๋ ๊ฐ์ฅ ๋๋ฆฌ ์ฌ์ฉ๋๋ ํฌ๋ก์ค ํ๋ซํผ ์ค๋์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค ํ๋์ผ. Windows, macOS, Linux ๋ชจ๋์์ ๋์ผํ ์ฝ๋๋ก ์๋ํ๊ฑฐ๋ ! ์ค์น๋ถํฐ ํด๋ณผ๊น?
// Ubuntu/Debian
sudo apt-get install portaudio19-dev
// macOS (Homebrew)
brew install portaudio
// Windows
// vcpkg๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ๊ณต์ ์ฌ์ดํธ์์ ๋ค์ด๋ก๋
vcpkg install portaudio
์ค์นํ์ผ๋ฉด ์ด์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ์์ ๋ฅผ ๋ง๋ค์ด๋ณด์. ์ฌ์ธํ(Sine Wave)๋ฅผ ์์ฑํด์ ์๋ฆฌ๋ฅผ ๋ด๋ ํ๋ก๊ทธ๋จ์ด์ผ. 440Hz ์ฌ์ธํ๋ ํผ์๋ ธ์ '๋ผ(A)' ์์ด๊ฑฐ๋ ! ๐น
#include <portaudio.h>
#include <cmath>
#include <iostream>
#define SAMPLE_RATE 44100
#define FRAMES_PER_BUFFER 256
#define FREQUENCY 440.0 // A4 ์
// ์ค๋์ค ๋ฐ์ดํฐ ๊ตฌ์กฐ์ฒด
struct AudioData {
float phase;
float phaseIncrement;
};
// ์ฝ๋ฐฑ ํจ์: ์ค์๊ฐ์ผ๋ก ์ค๋์ค ๋ฒํผ๋ฅผ ์ฑ์
static int audioCallback(const void* inputBuffer, void* outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void* userData) {
AudioData* data = (AudioData*)userData;
float* out = (float*)outputBuffer;
// ๊ฐ ํ๋ ์๋ง๋ค ์ฌ์ธํ ์์ฑ
for(unsigned long i = 0; i < framesPerBuffer; i++) {
*out++ = std::sin(data->phase); // ์ผ์ชฝ ์ฑ๋
*out++ = std::sin(data->phase); // ์ค๋ฅธ์ชฝ ์ฑ๋ (์คํ
๋ ์ค)
// ์์ ์
๋ฐ์ดํธ
data->phase += data->phaseIncrement;
if(data->phase >= 2.0 * M_PI) {
data->phase -= 2.0 * M_PI;
}
}
return paContinue;
}
int main() {
PaStream* stream;
PaError err;
AudioData data;
// ์์ ์ฆ๊ฐ๋ ๊ณ์ฐ
data.phase = 0.0f;
data.phaseIncrement = (2.0 * M_PI * FREQUENCY) / SAMPLE_RATE;
// PortAudio ์ด๊ธฐํ
err = Pa_Initialize();
if(err != paNoError) {
std::cerr << "PortAudio ์ด๊ธฐํ ์คํจ: " << Pa_GetErrorText(err) << std::endl;
return 1;
}
// ์คํธ๋ฆผ ์ด๊ธฐ
err = Pa_OpenDefaultStream(&stream,
0, // ์
๋ ฅ ์ฑ๋ ์์
2, // ์คํ
๋ ์ค ์ถ๋ ฅ
paFloat32, // 32๋นํธ float ์ํ
SAMPLE_RATE,
FRAMES_PER_BUFFER,
audioCallback,
&data);
if(err != paNoError) {
std::cerr << "์คํธ๋ฆผ ์ด๊ธฐ ์คํจ: " << Pa_GetErrorText(err) << std::endl;
Pa_Terminate();
return 1;
}
// ์คํธ๋ฆผ ์์
err = Pa_StartStream(stream);
if(err != paNoError) {
std::cerr << "์คํธ๋ฆผ ์์ ์คํจ: " << Pa_GetErrorText(err) << std::endl;
Pa_CloseStream(stream);
Pa_Terminate();
return 1;
}
std::cout << "440Hz ์ฌ์ธํ ์ฌ์ ์ค... (5์ด๊ฐ)" << std::endl;
Pa_Sleep(5000); // 5์ด ๋๊ธฐ
// ์ ๋ฆฌ
Pa_StopStream(stream);
Pa_CloseStream(stream);
Pa_Terminate();
std::cout << "์ฌ์ ์๋ฃ!" << std::endl;
return 0;
}
์ด ์ฝ๋์ ํต์ฌ์ audioCallback ํจ์์ผ. ์ด ํจ์๋ ์ค๋์ค ํ๋์จ์ด๊ฐ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ ๋๋ง๋ค ์๋์ผ๋ก ํธ์ถ๋ผ. ๋ง์น ๋ ์คํ ๋์์ ์ฃผ๋ฐฉ์ฅ์ด ๊ณ์ ์๋ฆฌ๋ฅผ ๋ง๋ค์ด๋ด๋ ๊ฒ์ฒ๋ผ!
์์(Phase): ์ฌ์ธํ์ ํ์ฌ ์์น๋ฅผ ๋ํ๋ด๋ ๊ฐ (0 ~ 2ฯ)
์์ ์ฆ๊ฐ๋: ๊ฐ ์ํ๋ง๋ค ์์์ด ์ผ๋ง๋ ์ฆ๊ฐํ๋์ง ๊ฒฐ์ (์ฃผํ์ ๊ฒฐ์ )
โก ์ค์๊ฐ ์ค๋์ค ์ฒ๋ฆฌ์ ํต์ฌ: ๋ฒํผ์ ๋ ์ดํด์
์ค์๊ฐ ์ค๋์ค ์ฒ๋ฆฌ์์ ๊ฐ์ฅ ์ค์ํ ๊ฐ๋ ์ด ๋ฐ๋ก ๋ฒํผ(Buffer)์ ๋ ์ดํด์(Latency)์ผ. ์ด ๋์ ์๋ก ํธ๋ ์ด๋์คํ ๊ด๊ณ์ ์์ด์, ํ๋๋ฅผ ์ข๊ฒ ํ๋ฉด ๋ค๋ฅธ ํ๋๊ฐ ๋๋น ์ง๋ ๊ด๊ณ์ง. ๐๏ธ
๐ช ๋ฒํผ ํฌ๊ธฐ์ ๋ง๋ฒ
๋ฒํผ๋ ์ค๋์ค ๋ฐ์ดํฐ๋ฅผ ์์๋ก ์ ์ฅํ๋ ๊ณต๊ฐ์ด์ผ. ๋๋ฌด ์์ผ๋ฉด CPU๊ฐ ๋ฐ๋ผ๊ฐ์ง ๋ชปํด์ ์๋ฆฌ๊ฐ ๋๊ธฐ๊ณ (์ธ๋๋ฐ), ๋๋ฌด ํฌ๋ฉด ์ง์ฐ์๊ฐ์ด ๊ธธ์ด์ ธ์ ์ค์๊ฐ ๋ฐ์์ด ๋๋ ค์ ธ.
์๋ฅผ ๋ค์ด๋ณผ๊ฒ:
๋ ์ดํด์ = 256 / 44,100 = ์ฝ 5.8ms โจ (๊ฒ์์ด๋ ์ ๊ธฐ ์ฐ์ฃผ์ ์ ํฉ)
๋ฒํผ ํฌ๊ธฐ 2048 ์ํ @ 44,100Hz
๋ ์ดํด์ = 2048 / 44,100 = ์ฝ 46.4ms ๐ (์์ ์ฌ์์๋ ๊ด์ฐฎ์ง๋ง ์ค์๊ฐ ์ฐ์ฃผ๋ ํ๋ค์ด)
// ๋ค์ํ ๋ฒํผ ํฌ๊ธฐ ํ
์คํธ
#include <iostream>
void calculateLatency(int bufferSize, int sampleRate) {
double latencyMs = (bufferSize * 1000.0) / sampleRate;
std::cout << "๋ฒํผ ํฌ๊ธฐ: " << bufferSize
<< " | ๋ ์ดํด์: " << latencyMs << "ms" << std::endl;
if(latencyMs < 10) {
std::cout << " โ ์ค์๊ฐ ์
๊ธฐ ์ฐ์ฃผ ๊ฐ๋ฅ! ๐ธ" << std::endl;
} else if(latencyMs < 30) {
std::cout << " โ ๊ฒ์ ์ค๋์ค์ ์ ํฉ ๐ฎ" << std::endl;
} else {
std::cout << " โ ์ผ๋ฐ ์ฌ์์ฉ ๐ต" << std::endl;
}
}
int main() {
int sampleRate = 44100;
std::cout << "=== ๋ฒํผ ํฌ๊ธฐ๋ณ ๋ ์ดํด์ ๋ถ์ ===" << std::endl;
calculateLatency(64, sampleRate);
calculateLatency(128, sampleRate);
calculateLatency(256, sampleRate);
calculateLatency(512, sampleRate);
calculateLatency(1024, sampleRate);
calculateLatency(2048, sampleRate);
return 0;
}
๐๏ธ DSP ๊ธฐ์ด: ์ค๋์ค ์ ํธ ์ฒ๋ฆฌ
DSP(Digital Signal Processing)๋ ๋์งํธ ์ ํธ๋ฅผ ์ํ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๊ธฐ์ ์ด์ผ. ์ค๋์ค์์๋ ํํฐ, ์ดํ๋ผ์ด์ , ๋ฆฌ๋ฒ๋ธ, ๋๋ ์ด ๊ฐ์ ํจ๊ณผ๋ฅผ ๋ง๋๋ ๋ฐ ์ฌ์ฉ๋ผ. ๋ง์น ์ฌ์ง ํธ์ง ์ฑ์ ํํฐ์ฒ๋ผ! ๐ธ
๐ ๋ณผ๋ฅจ ์กฐ์ : ๊ฐ์ฅ ๊ฐ๋จํ DSP
๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ DSP๋ ๋ณผ๋ฅจ ์กฐ์ ์ด์ผ. ๊ฐ ์ํ์ ํน์ ๊ฐ์ ๊ณฑํ๊ธฐ๋ง ํ๋ฉด ๋ผ. ๊ฐ๋จํ์ง? ํ์ง๋ง ์ฌ๊ธฐ์๋ ํจ์ ์ด ์์ด!
#include <vector>
#include <algorithm>
#include <cmath>
class VolumeControl {
private:
float currentGain;
float targetGain;
float smoothingFactor;
public:
VolumeControl() : currentGain(1.0f), targetGain(1.0f), smoothingFactor(0.001f) {}
// ๋ฐ์๋ฒจ์ ์ ํ ๊ฒ์ธ์ผ๋ก ๋ณํ
void setVolumeDB(float db) {
targetGain = std::pow(10.0f, db / 20.0f);
}
// 0.0 ~ 1.0 ๋ฒ์์ ๋ณผ๋ฅจ ์ค์
void setVolumeLinear(float volume) {
targetGain = std::clamp(volume, 0.0f, 1.0f);
}
// ์ค๋์ค ๋ฒํผ ์ฒ๋ฆฌ (์ค๋ฌด๋ฉ ์ ์ฉ)
void process(float* buffer, int numSamples) {
for(int i = 0; i < numSamples; i++) {
// ๋ถ๋๋ฌ์ด ๋ณผ๋ฅจ ๋ณํ (ํด๋ฆญ ๋
ธ์ด์ฆ ๋ฐฉ์ง)
currentGain += (targetGain - currentGain) * smoothingFactor;
buffer[i] *= currentGain;
}
}
// ์ฆ์ ์ ์ฉ (ํด๋ฆญ ๋
ธ์ด์ฆ ๋ฐ์ ๊ฐ๋ฅ)
void processImmediate(float* buffer, int numSamples) {
for(int i = 0; i < numSamples; i++) {
buffer[i] *= targetGain;
}
}
};
// ์ฌ์ฉ ์์
int main() {
VolumeControl volume;
std::vector<float> audioBuffer(1024);
// ๋ฒํผ๋ฅผ ํ
์คํธ ๋ฐ์ดํฐ๋ก ์ฑ์
for(int i = 0; i < audioBuffer.size(); i++) {
audioBuffer[i] = std::sin(2.0 * M_PI * 440.0 * i / 44100.0);
}
// ๋ณผ๋ฅจ์ -6dB๋ก ์ค์ (์ฝ 50% ๊ฐ์)
volume.setVolumeDB(-6.0f);
volume.process(audioBuffer.data(), audioBuffer.size());
std::cout << "๋ณผ๋ฅจ ์ฒ๋ฆฌ ์๋ฃ!" << std::endl;
return 0;
}
๋ณผ๋ฅจ์ ๊ฐ์๊ธฐ ๋ฐ๊พธ๋ฉด ํํ์ด ๊ธ๊ฒฉํ๊ฒ ๋ณํด์ '๋ฑ' ํ๋ ํด๋ฆญ ๋ ธ์ด์ฆ๊ฐ ๋ฐ์ํด. ๋ง์น ์ ๋ฑ์ ๊ฐ์๊ธฐ ์ผ๊ณ ๋๋ ๊ฒ์ฒ๋ผ! ๊ทธ๋์ ์ ์ง์ ์ผ๋ก ๋ณํ์ํค๋ ์ค๋ฌด๋ฉ์ด ํ์ํ ๊ฑฐ์ผ. ๐๏ธ
๐ธ ๋ก์ฐํจ์ค ํํฐ ๊ตฌํํ๊ธฐ
ํํฐ๋ ํน์ ์ฃผํ์ ๋์ญ์ ๊ฐ์กฐํ๊ฑฐ๋ ์ ๊ฑฐํ๋ DSP ํจ๊ณผ์ผ. ๋ก์ฐํจ์ค ํํฐ๋ ๋ฎ์ ์ฃผํ์๋ง ํต๊ณผ์ํค๊ณ ๋์ ์ฃผํ์๋ ์ฐจ๋จํด. ๋ฒ ์ด์ค ๋ถ์คํฐ๋ ๊ณ ์ ์ ๊ฑฐ์ ์ฌ์ฉ๋ผ!
class SimpleLopassFilter {
private:
float cutoffFrequency;
float sampleRate;
float alpha;
float previousOutput;
void calculateCoefficients() {
float rc = 1.0f / (2.0f * M_PI * cutoffFrequency);
float dt = 1.0f / sampleRate;
alpha = dt / (rc + dt);
}
public:
SimpleLopassFilter(float cutoff, float sr)
: cutoffFrequency(cutoff), sampleRate(sr), previousOutput(0.0f) {
calculateCoefficients();
}
void setCutoff(float cutoff) {
cutoffFrequency = cutoff;
calculateCoefficients();
}
// ๋จ์ผ ์ํ ์ฒ๋ฆฌ
float processSample(float input) {
previousOutput = previousOutput + alpha * (input - previousOutput);
return previousOutput;
}
// ๋ฒํผ ์ฒ๋ฆฌ
void process(float* buffer, int numSamples) {
for(int i = 0; i < numSamples; i++) {
buffer[i] = processSample(buffer[i]);
}
}
void reset() {
previousOutput = 0.0f;
}
};
// ์ฌ์ฉ ์์
int main() {
const int SAMPLE_RATE = 44100;
SimpleLopassFilter filter(1000.0f, SAMPLE_RATE); // 1kHz ์ปท์คํ
std::vector<float> audioBuffer(SAMPLE_RATE); // 1์ด ๋ถ๋
// ์ฌ๋ฌ ์ฃผํ์๊ฐ ์์ธ ์ ํธ ์์ฑ
for(int i = 0; i < audioBuffer.size(); i++) {
float t = i / (float)SAMPLE_RATE;
audioBuffer[i] = std::sin(2.0 * M_PI * 440.0 * t) // 440Hz
+ std::sin(2.0 * M_PI * 2000.0 * t) // 2000Hz
+ std::sin(2.0 * M_PI * 5000.0 * t); // 5000Hz
}
std::cout << "ํํฐ ์ ์ฉ ์ ์ ํธ: 440Hz + 2000Hz + 5000Hz" << std::endl;
// ํํฐ ์ ์ฉ
filter.process(audioBuffer.data(), audioBuffer.size());
std::cout << "ํํฐ ์ ์ฉ ํ: ์ฃผ๋ก 440Hz๋ง ๋จ์ (1kHz ์ดํ)" << std::endl;
return 0;
}
๐๏ธ ์ค์ ํ๋ก์ ํธ: ๊ฐ๋จํ ์ค๋์ค ๋ฏน์ ๋ง๋ค๊ธฐ
์ด์ ๋ฐฐ์ด ๋ด์ฉ์ ์ข ํฉํด์ ์ค์ ๋ก ์ฌ์ฉํ ์ ์๋ ์ค๋์ค ๋ฏน์๋ฅผ ๋ง๋ค์ด๋ณด์! ์ฌ๋ฌ ์ค๋์ค ์์ค๋ฅผ ๋ฏน์ฑํ๊ณ , ๊ฐ๊ฐ์ ๋ณผ๋ฅจ๊ณผ ํฌ(์ข์ฐ ๋ฐธ๋ฐ์ค)์ ์ ์ฉํ๋ ํ๋ก๊ทธ๋จ์ด์ผ. ๐๏ธ
#include <vector>
#include <memory>
#include <algorithm>
#include <cmath>
// ์ค๋์ค ์ฑ๋ ํด๋์ค
class AudioChannel {
private:
std::vector<float> audioData;
float volume;
float pan; // -1.0 (์ผ์ชฝ) ~ 0.0 (์ค์) ~ 1.0 (์ค๋ฅธ์ชฝ)
bool muted;
bool solo;
int playPosition;
public:
AudioChannel() : volume(1.0f), pan(0.0f), muted(false),
solo(false), playPosition(0) {}
void loadAudio(const std::vector<float>& data) {
audioData = data;
playPosition = 0;
}
void setVolume(float vol) {
volume = std::clamp(vol, 0.0f, 2.0f); // ์ต๋ 2๋ฐฐ ์ฆํญ
}
void setPan(float p) {
pan = std::clamp(p, -1.0f, 1.0f);
}
void setMute(bool m) { muted = m; }
void setSolo(bool s) { solo = s; }
bool isSolo() const { return solo; }
// ์คํ
๋ ์ค ์ํ ๊ฐ์ ธ์ค๊ธฐ
void getStereoSample(float& left, float& right) {
if(playPosition >= audioData.size() || muted) {
left = right = 0.0f;
return;
}
float sample = audioData[playPosition++] * volume;
// ํฌ ์ ์ฉ (Equal Power Panning)
float panAngle = pan * M_PI / 4.0f; // -45๋ ~ +45๋
left = sample * std::cos(panAngle - M_PI / 4.0f);
right = sample * std::sin(panAngle + M_PI / 4.0f);
}
void reset() {
playPosition = 0;
}
bool isFinished() const {
return playPosition >= audioData.size();
}
};
// ๋ฏน์ ํด๋์ค
class AudioMixer {
private:
std::vector<std::unique_ptr<AudioChannel>> channels;
float masterVolume;
int sampleRate;
public:
AudioMixer(int sr) : masterVolume(1.0f), sampleRate(sr) {}
AudioChannel* addChannel() {
channels.push_back(std::make_unique<AudioChannel>());
return channels.back().get();
}
void setMasterVolume(float vol) {
masterVolume = std::clamp(vol, 0.0f, 2.0f);
}
// ๋ชจ๋ ์ฑ๋์ ๋ฏน์ฑํ์ฌ ์คํ
๋ ์ค ์ถ๋ ฅ ์์ฑ
void mixToStereo(float* leftOut, float* rightOut, int numSamples) {
// ์ถ๋ ฅ ๋ฒํผ ์ด๊ธฐํ
std::fill(leftOut, leftOut + numSamples, 0.0f);
std::fill(rightOut, rightOut + numSamples, 0.0f);
// ์๋ก ์ฑ๋ ํ์ธ
bool hasSolo = false;
for(const auto& channel : channels) {
if(channel->isSolo()) {
hasSolo = true;
break;
}
}
// ๊ฐ ์ฑ๋ ๋ฏน์ฑ
for(int i = 0; i < numSamples; i++) {
float mixedLeft = 0.0f;
float mixedRight = 0.0f;
for(auto& channel : channels) {
// ์๋ก ๋ชจ๋์ผ ๋๋ ์๋ก ์ฑ๋๋ง ์ฌ์
if(hasSolo && !channel->isSolo()) {
continue;
}
float left, right;
channel->getStereoSample(left, right);
mixedLeft += left;
mixedRight += right;
}
// ๋ง์คํฐ ๋ณผ๋ฅจ ์ ์ฉ ๋ฐ ํด๋ฆฌํ ๋ฐฉ์ง
leftOut[i] = std::clamp(mixedLeft * masterVolume, -1.0f, 1.0f);
rightOut[i] = std::clamp(mixedRight * masterVolume, -1.0f, 1.0f);
}
}
void resetAllChannels() {
for(auto& channel : channels) {
channel->reset();
}
}
int getChannelCount() const {
return channels.size();
}
};
// ์ฌ์ฉ ์์
int main() {
const int SAMPLE_RATE = 44100;
const int DURATION = 2; // 2์ด
const int BUFFER_SIZE = SAMPLE_RATE * DURATION;
AudioMixer mixer(SAMPLE_RATE);
// ์ฑ๋ 1: 440Hz ์ฌ์ธํ (์ค์)
AudioChannel* channel1 = mixer.addChannel();
std::vector<float> sine440(BUFFER_SIZE);
for(int i = 0; i < BUFFER_SIZE; i++) {
sine440[i] = std::sin(2.0 * M_PI * 440.0 * i / SAMPLE_RATE) * 0.3f;
}
channel1->loadAudio(sine440);
channel1->setVolume(0.8f);
channel1->setPan(0.0f); // ์ค์
// ์ฑ๋ 2: 880Hz ์ฌ์ธํ (์ผ์ชฝ)
AudioChannel* channel2 = mixer.addChannel();
std::vector<float> sine880(BUFFER_SIZE);
for(int i = 0; i < BUFFER_SIZE; i++) {
sine880[i] = std::sin(2.0 * M_PI * 880.0 * i / SAMPLE_RATE) * 0.3f;
}
channel2->loadAudio(sine880);
channel2->setVolume(0.6f);
channel2->setPan(-0.7f); // ์ผ์ชฝ
// ์ฑ๋ 3: 220Hz ์ฌ์ธํ (์ค๋ฅธ์ชฝ)
AudioChannel* channel3 = mixer.addChannel();
std::vector<float> sine220(BUFFER_SIZE);
for(int i = 0; i < BUFFER_SIZE; i++) {
sine220[i] = std::sin(2.0 * M_PI * 220.0 * i / SAMPLE_RATE) * 0.3f;
}
channel3->loadAudio(sine220);
channel3->setVolume(0.7f);
channel3->setPan(0.7f); // ์ค๋ฅธ์ชฝ
// ๋ฏน์ฑ ์ํ
std::vector<float> leftOutput(BUFFER_SIZE);
std::vector<float> rightOutput(BUFFER_SIZE);
mixer.setMasterVolume(0.9f);
mixer.mixToStereo(leftOutput.data(), rightOutput.data(), BUFFER_SIZE);
std::cout << "๋ฏน์ฑ ์๋ฃ!" << std::endl;
std::cout << "์ฑ๋ ์: " << mixer.getChannelCount() << std::endl;
std::cout << "์ด ์ํ ์: " << BUFFER_SIZE << std::endl;
// ์ฌ๊ธฐ์ leftOutput๊ณผ rightOutput์ ํ์ผ๋ก ์ ์ฅํ๊ฑฐ๋ ์ฌ์ํ ์ ์์
return 0;
}
1. ๋ฉํฐ ์ฑ๋ ์ง์: ์ฌ๋ฌ ์ค๋์ค ์์ค๋ฅผ ๋์์ ์ฒ๋ฆฌ
2. ๋ณผ๋ฅจ ์ปจํธ๋กค: ๊ฐ ์ฑ๋๊ณผ ๋ง์คํฐ ๋ณผ๋ฅจ ๋ ๋ฆฝ ์ ์ด
3. ํฌ(Pan): ์คํ ๋ ์ค ๊ณต๊ฐ์์ ์๋ฆฌ์ ์์น ์กฐ์
4. ์๋ก/๋ฎคํธ: ํน์ ์ฑ๋๋ง ๋ฃ๊ฑฐ๋ ์ ์ธ
5. ํด๋ฆฌํ ๋ฐฉ์ง: ์ถ๋ ฅ์ด -1.0 ~ 1.0 ๋ฒ์๋ฅผ ๋ฒ์ด๋์ง ์๋๋ก ์ ํ
๐ผ WAV ํ์ผ ์ฝ๊ธฐ์ ์ฐ๊ธฐ
์ค์ ์ค๋์ค ํ์ผ์ ๋ค๋ฃจ๋ ค๋ฉด ํ์ผ ํฌ๋งท์ ์ดํดํด์ผ ํด. WAV๋ ๊ฐ์ฅ ๊ฐ๋จํ ์ค๋์ค ํฌ๋งท ์ค ํ๋์ผ. ์์ถ๋์ง ์์ PCM ๋ฐ์ดํฐ๋ฅผ ๋ด๊ณ ์์ด์ ์ฒ๋ฆฌํ๊ธฐ ์ฝ๊ฑฐ๋ ! ๐
๐ WAV ํ์ผ ๊ตฌ์กฐ
WAV ํ์ผ์ RIFF(Resource Interchange File Format) ์ปจํ ์ด๋๋ฅผ ์ฌ์ฉํด. ํค๋์ ์ํ๋ ์ดํธ, ๋นํธ ๋์ค, ์ฑ๋ ์ ๊ฐ์ ๋ฉํ๋ฐ์ดํฐ๊ฐ ๋ค์ด์๊ณ , ๊ทธ ๋ค์ ์ค์ ์ค๋์ค ๋ฐ์ดํฐ๊ฐ ๋ฐ๋ผ์.
#include <fstream>
#include <vector>
#include <cstring>
#include <iostream>
// WAV ํ์ผ ํค๋ ๊ตฌ์กฐ์ฒด
struct WavHeader {
// RIFF ์ฒญํฌ
char riffHeader[4]; // "RIFF"
uint32_t fileSize; // ํ์ผ ํฌ๊ธฐ - 8
char waveHeader[4]; // "WAVE"
// fmt ์ฒญํฌ
char fmtHeader[4]; // "fmt "
uint32_t fmtChunkSize; // fmt ์ฒญํฌ ํฌ๊ธฐ (๋ณดํต 16)
uint16_t audioFormat; // ์ค๋์ค ํฌ๋งท (1 = PCM)
uint16_t numChannels; // ์ฑ๋ ์
uint32_t sampleRate; // ์ํ๋ ์ดํธ
uint32_t byteRate; // ์ด๋น ๋ฐ์ดํธ ์
uint16_t blockAlign; // ๋ธ๋ก ์ ๋ ฌ
uint16_t bitsPerSample; // ๋นํธ ๋์ค
// data ์ฒญํฌ
char dataHeader[4]; // "data"
uint32_t dataSize; // ๋ฐ์ดํฐ ํฌ๊ธฐ
};
class WavFile {
private:
WavHeader header;
std::vector<float> audioData; // -1.0 ~ 1.0 ๋ฒ์๋ก ์ ๊ทํ๋ ๋ฐ์ดํฐ
// 16๋นํธ ์ ์๋ฅผ float๋ก ๋ณํ
float int16ToFloat(int16_t sample) {
return sample / 32768.0f;
}
// float๋ฅผ 16๋นํธ ์ ์๋ก ๋ณํ
int16_t floatToInt16(float sample) {
sample = std::clamp(sample, -1.0f, 1.0f);
return static_cast<int16_t>(sample * 32767.0f);
}
public:
WavFile() {
std::memset(&header, 0, sizeof(WavHeader));
}
// WAV ํ์ผ ์ฝ๊ธฐ
bool load(const std::string& filename) {
std::ifstream file(filename, std::ios::binary);
if(!file.is_open()) {
std::cerr << "ํ์ผ์ ์ด ์ ์์ต๋๋ค: " << filename << std::endl;
return false;
}
// ํค๋ ์ฝ๊ธฐ
file.read(reinterpret_cast<char*>(&header), sizeof(WavHeader));
// ํค๋ ๊ฒ์ฆ
if(std::strncmp(header.riffHeader, "RIFF", 4) != 0 ||
std::strncmp(header.waveHeader, "WAVE", 4) != 0) {
std::cerr << "์ ํจํ์ง ์์ WAV ํ์ผ์
๋๋ค." << std::endl;
return false;
}
if(header.audioFormat != 1) {
std::cerr << "์์ถ๋ WAV๋ ์ง์ํ์ง ์์ต๋๋ค." << std::endl;
return false;
}
// ์ค๋์ค ๋ฐ์ดํฐ ์ฝ๊ธฐ
int numSamples = header.dataSize / (header.bitsPerSample / 8);
audioData.resize(numSamples);
if(header.bitsPerSample == 16) {
std::vector<int16_t> rawData(numSamples);
file.read(reinterpret_cast<char*>(rawData.data()), header.dataSize);
for(int i = 0; i < numSamples; i++) {
audioData[i] = int16ToFloat(rawData[i]);
}
} else {
std::cerr << "16๋นํธ WAV๋ง ์ง์ํฉ๋๋ค." << std::endl;
return false;
}
file.close();
std::cout << "WAV ํ์ผ ๋ก๋ ์๋ฃ!" << std::endl;
std::cout << " ์ํ๋ ์ดํธ: " << header.sampleRate << " Hz" << std::endl;
std::cout << " ์ฑ๋ ์: " << header.numChannels << std::endl;
std::cout << " ๋นํธ ๋์ค: " << header.bitsPerSample << " bit" << std::endl;
std::cout << " ๊ธธ์ด: " << numSamples / header.sampleRate / header.numChannels
<< " ์ด" << std::endl;
return true;
}
// WAV ํ์ผ ์ ์ฅ
bool save(const std::string& filename, int sampleRate, int numChannels) {
std::ofstream file(filename, std::ios::binary);
if(!file.is_open()) {
std::cerr << "ํ์ผ์ ์์ฑํ ์ ์์ต๋๋ค: " << filename << std::endl;
return false;
}
// ํค๋ ์ค์
std::memcpy(header.riffHeader, "RIFF", 4);
std::memcpy(header.waveHeader, "WAVE", 4);
std::memcpy(header.fmtHeader, "fmt ", 4);
std::memcpy(header.dataHeader, "data", 4);
header.fmtChunkSize = 16;
header.audioFormat = 1; // PCM
header.numChannels = numChannels;
header.sampleRate = sampleRate;
header.bitsPerSample = 16;
header.blockAlign = numChannels * header.bitsPerSample / 8;
header.byteRate = sampleRate * header.blockAlign;
header.dataSize = audioData.size() * sizeof(int16_t);
header.fileSize = 36 + header.dataSize;
// ํค๋ ์ฐ๊ธฐ
file.write(reinterpret_cast<const char*>(&header), sizeof(WavHeader));
// ์ค๋์ค ๋ฐ์ดํฐ ์ฐ๊ธฐ
for(float sample : audioData) {
int16_t intSample = floatToInt16(sample);
file.write(reinterpret_cast<const char*>(&intSample), sizeof(int16_t));
}
file.close();
std::cout << "WAV ํ์ผ ์ ์ฅ ์๋ฃ: " << filename << std::endl;
return true;
}
// ์ค๋์ค ๋ฐ์ดํฐ ์ ๊ทผ
std::vector<float>& getData() { return audioData; }
const std::vector<float>& getData() const { return audioData; }
void setData(const std::vector<float>& data) {
audioData = data;
}
int getSampleRate() const { return header.sampleRate; }
int getNumChannels() const { return header.numChannels; }
};
// ์ฌ์ฉ ์์
int main() {
// WAV ํ์ผ ์์ฑ ์์
WavFile wav;
const int SAMPLE_RATE = 44100;
const int DURATION = 3; // 3์ด
const int NUM_SAMPLES = SAMPLE_RATE * DURATION;
std::vector<float> audioData(NUM_SAMPLES);
// ์ค์ ํค ์์ฑ (์ฃผํ์๊ฐ ์ ์ ์ฌ๋ผ๊ฐ)
for(int i = 0; i < NUM_SAMPLES; i++) {
float t = i / (float)SAMPLE_RATE;
float frequency = 200.0f + (t / DURATION) * 2000.0f; // 200Hz ~ 2200Hz
audioData[i] = std::sin(2.0 * M_PI * frequency * t) * 0.5f;
}
wav.setData(audioData);
wav.save("sweep_tone.wav", SAMPLE_RATE, 1); // ๋ชจ๋
ธ
// WAV ํ์ผ ์ฝ๊ธฐ ์์
WavFile loadedWav;
if(loadedWav.load("sweep_tone.wav")) {
std::cout << "ํ์ผ์ ์ฑ๊ณต์ ์ผ๋ก ์ฝ์์ต๋๋ค!" << std::endl;
// ๋ฐ์ดํฐ ์ฒ๋ฆฌ (์: ๋ณผ๋ฅจ 50% ๊ฐ์)
auto& data = loadedWav.getData();
for(float& sample : data) {
sample *= 0.5f;
}
loadedWav.save("sweep_tone_quiet.wav",
loadedWav.getSampleRate(),
loadedWav.getNumChannels());
}
return 0;
}
๐ฎ ์ค์๊ฐ ์ค๋์ค ํจ๊ณผ: ๋๋ ์ด์ ๋ฆฌ๋ฒ๋ธ
์ด์ ์ข ๋ ์ฌ๋ฏธ์๋ ํจ๊ณผ๋ฅผ ๋ง๋ค์ด๋ณด์! ๋๋ ์ด(Delay)์ ๋ฆฌ๋ฒ๋ธ(Reverb)๋ ๊ณต๊ฐ๊ฐ์ ๋ง๋๋ ๋ํ์ ์ธ ํจ๊ณผ์ผ. ์ฝ์ํธํ์์ ๋ฃ๋ ๊ฒ ๊ฐ์ ๋๋์ ๋ง๋ค ์ ์์ง! ๐ช
โฑ๏ธ ๋๋ ์ด ํจ๊ณผ ๊ตฌํ
๋๋ ์ด๋ ์๋ณธ ์๋ฆฌ๋ฅผ ์ผ์ ์๊ฐ ํ์ ๋ฐ๋ณตํ๋ ํจ๊ณผ์ผ. ์์ฝ(Echo)๋ผ๊ณ ๋ ํ์ง. ๊ตฌํ์ ๊ฐ๋จํด - ๋ง ๋ฒํผ(Circular Buffer)๋ฅผ ์ฌ์ฉํ๋ฉด ๋ผ!
#include <vector>
#include <algorithm>
class DelayEffect {
private:
std::vector<float> delayBuffer;
int writePosition;
int delayTimeInSamples;
float feedback;
float wetLevel;
float dryLevel;
public:
DelayEffect(int maxDelayMs, int sampleRate) {
int maxDelaySamples = (maxDelayMs * sampleRate) / 1000;
delayBuffer.resize(maxDelaySamples, 0.0f);
writePosition = 0;
delayTimeInSamples = maxDelaySamples / 2;
feedback = 0.5f;
wetLevel = 0.5f;
dryLevel = 1.0f;
}
void setDelayTime(int delayMs, int sampleRate) {
delayTimeInSamples = std::min((delayMs * sampleRate) / 1000,
(int)delayBuffer.size());
}
void setFeedback(float fb) {
feedback = std::clamp(fb, 0.0f, 0.95f); // 0.95 ์ด์์ด๋ฉด ๋ฐ์ฐ ์ํ
}
void setWetDryMix(float wet, float dry) {
wetLevel = std::clamp(wet, 0.0f, 1.0f);
dryLevel = std::clamp(dry, 0.0f, 1.0f);
}
float processSample(float input) {
// ์ฝ๊ธฐ ์์น ๊ณ์ฐ
int readPosition = writePosition - delayTimeInSamples;
if(readPosition < 0) {
readPosition += delayBuffer.size();
}
// ๋๋ ์ด๋ ์ํ ์ฝ๊ธฐ
float delayedSample = delayBuffer[readPosition];
// ์ ์ํ ๊ณ์ฐ (์
๋ ฅ + ํผ๋๋ฐฑ)
float newSample = input + (delayedSample * feedback);
// ๋ฒํผ์ ์ฐ๊ธฐ
delayBuffer[writePosition] = newSample;
// ์ฐ๊ธฐ ์์น ์
๋ฐ์ดํธ
writePosition = (writePosition + 1) % delayBuffer.size();
// ์ถ๋ ฅ = ๋๋ผ์ด + ์ป
return (input * dryLevel) + (delayedSample * wetLevel);
}
void process(float* buffer, int numSamples) {
for(int i = 0; i < numSamples; i++) {
buffer[i] = processSample(buffer[i]);
}
}
void clear() {
std::fill(delayBuffer.begin(), delayBuffer.end(), 0.0f);
writePosition = 0;
}
};
// ์ฌ์ฉ ์์
int main() {
const int SAMPLE_RATE = 44100;
DelayEffect delay(1000, SAMPLE_RATE); // ์ต๋ 1์ด ๋๋ ์ด
delay.setDelayTime(250, SAMPLE_RATE); // 250ms ๋๋ ์ด
delay.setFeedback(0.6f); // 60% ํผ๋๋ฐฑ
delay.setWetDryMix(0.4f, 1.0f); // 40% ์ป, 100% ๋๋ผ์ด
// ํ
์คํธ ์ ํธ ์์ฑ (์งง์ ํ์ค)
std::vector<float> audioBuffer(SAMPLE_RATE * 2); // 2์ด
for(int i = 0; i < 100; i++) {
audioBuffer[i] = 1.0f; // ์ฒ์ 100 ์ํ๋ง 1.0
}
// ๋๋ ์ด ์ ์ฉ
delay.process(audioBuffer.data(), audioBuffer.size());
std::cout << "๋๋ ์ด ํจ๊ณผ ์ ์ฉ ์๋ฃ!" << std::endl;
std::cout << "์๋ณธ ํ์ค๊ฐ 250ms๋ง๋ค ๋ฐ๋ณต๋ฉ๋๋ค." << std::endl;
return 0;
}
๐๏ธ ๊ฐ๋จํ ๋ฆฌ๋ฒ๋ธ ๊ตฌํ
๋ฆฌ๋ฒ๋ธ๋ ๊ณต๊ฐ์ ์ํฅ์ ์๋ฎฌ๋ ์ด์ ํ๋ ํจ๊ณผ์ผ. ์ค์ ๋ก๋ ๋งค์ฐ ๋ณต์กํ์ง๋ง, ์ฌ๋ฌ ๊ฐ์ ๋๋ ์ด๋ฅผ ์กฐํฉํ๋ฉด ๊ฐ๋จํ ๋ฆฌ๋ฒ๋ธ๋ฅผ ๋ง๋ค ์ ์์ด. ์ด๊ฑธ Schroeder Reverb๋ผ๊ณ ํด!
class SimpleReverb {
private:
std::vector<DelayEffect> combFilters;
std::vector<DelayEffect> allpassFilters;
float wetLevel;
float dryLevel;
public:
SimpleReverb(int sampleRate) : wetLevel(0.3f), dryLevel(0.7f) {
// Comb ํํฐ (๋ณ๋ ฌ ๊ตฌ์กฐ)
combFilters.emplace_back(100, sampleRate);
combFilters[0].setDelayTime(29, sampleRate);
combFilters[0].setFeedback(0.7f);
combFilters.emplace_back(100, sampleRate);
combFilters[1].setDelayTime(37, sampleRate);
combFilters[1].setFeedback(0.7f);
combFilters.emplace_back(100, sampleRate);
combFilters[2].setDelayTime(41, sampleRate);
combFilters[2].setFeedback(0.7f);
combFilters.emplace_back(100, sampleRate);
combFilters[3].setDelayTime(43, sampleRate);
combFilters[3].setFeedback(0.7f);
// Allpass ํํฐ (์ง๋ ฌ ๊ตฌ์กฐ)
allpassFilters.emplace_back(50, sampleRate);
allpassFilters[0].setDelayTime(5, sampleRate);
allpassFilters[0].setFeedback(0.5f);
allpassFilters.emplace_back(50, sampleRate);
allpassFilters[1].setDelayTime(11, sampleRate);
allpassFilters[1].setFeedback(0.5f);
}
void setWetDryMix(float wet, float dry) {
wetLevel = std::clamp(wet, 0.0f, 1.0f);
dryLevel = std::clamp(dry, 0.0f, 1.0f);
}
float processSample(float input) {
// Comb ํํฐ ๋ณ๋ ฌ ์ฒ๋ฆฌ
float combOutput = 0.0f;
for(auto& comb : combFilters) {
combOutput += comb.processSample(input);
}
combOutput /= combFilters.size();
// Allpass ํํฐ ์ง๋ ฌ ์ฒ๋ฆฌ
float allpassOutput = combOutput;
for(auto& allpass : allpassFilters) {
allpassOutput = allpass.processSample(allpassOutput);
}
// ์ต์ข
๋ฏน์ค
return (input * dryLevel) + (allpassOutput * wetLevel);
}
void process(float* buffer, int numSamples) {
for(int i = 0; i < numSamples; i++) {
buffer[i] = processSample(buffer[i]);
}
}
void clear() {
for(auto& comb : combFilters) {
comb.clear();
}
for(auto& allpass : allpassFilters) {
allpass.clear();
}
}
};
์ค์ ๊ณต๊ฐ์์๋ ์๋ฆฌ๊ฐ ๋ฒฝ, ์ฒ์ฅ, ๋ฐ๋ฅ์ ์์ฒ ๋ฒ ๋ฐ์ฌ๋ผ. ์ด ๋ชจ๋ ๋ฐ์ฌ์์ด ํฉ์ณ์ ธ์ ์ํฅ์ด ๋ง๋ค์ด์ง๋ ๊ฑฐ์ผ. Comb ํํฐ๋ ์ด๋ฐ ๋ฐ์ฌ๋ฅผ ์๋ฎฌ๋ ์ด์ ํ๊ณ , Allpass ํํฐ๋ ์์์ ์์ฐ์ค๋ฝ๊ฒ ๋ง๋ค์ด์ค. ๐ต
๐ฏ ์ฑ๋ฅ ์ต์ ํ: ๋น ๋ฅธ ์ค๋์ค ์ฒ๋ฆฌ๋ฅผ ์ํ ํ
์ค์๊ฐ ์ค๋์ค ์ฒ๋ฆฌ์์ ์ฑ๋ฅ์ ์๋ช ์ด์ผ! CPU๊ฐ ๋ฒํผ๋ฅผ ์ ๋ ์ฑ์ฐ์ง ๋ชปํ๋ฉด ์๋ฆฌ๊ฐ ๋๊ธฐ๊ฑฐ๋ . ์ฌ๊ธฐ ๋ช ๊ฐ์ง ์ต์ ํ ๊ธฐ๋ฒ์ ์๋ ค์ค๊ฒ. ๐
1. SIMD ๋ช ๋ น์ด ์ฌ์ฉ: SSE, AVX๋ก ์ฌ๋ฌ ์ํ์ ๋์์ ์ฒ๋ฆฌ
2. ์บ์ ์นํ์ ์ฝ๋: ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ ํจํด ์ต์ ํ
3. ๋ถํ์ํ ๋์ ํ ๋น ์ ๊ฑฐ: ์ค๋์ค ์ฝ๋ฐฑ์์ new/delete ๊ธ์ง!
4. ๋ฃฉ์ ํ ์ด๋ธ: ๋ณต์กํ ๊ณ์ฐ์ ๋ฏธ๋ฆฌ ๊ณ์ฐํด์ ํ ์ด๋ธ๋ก
5. ๋ฉํฐ์ค๋ ๋ฉ: ๋ฌด๊ฑฐ์ด ์ฒ๋ฆฌ๋ ๋ณ๋ ์ค๋ ๋์์
๐ฅ SIMD๋ฅผ ํ์ฉํ ๋ฒกํฐ ์ฐ์ฐ
#include <immintrin.h> // SSE/AVX ํค๋
#include <vector>
#include <chrono>
// ์ผ๋ฐ์ ์ธ ๋ณผ๋ฅจ ์กฐ์
void applyVolumeScalar(float* buffer, int numSamples, float volume) {
for(int i = 0; i < numSamples; i++) {
buffer[i] *= volume;
}
}
// SSE๋ฅผ ์ฌ์ฉํ ๋ณผ๋ฅจ ์กฐ์ (4๋ฐฐ ๋น ๋ฆ!)
void applyVolumeSSE(float* buffer, int numSamples, float volume) {
__m128 volumeVec = _mm_set1_ps(volume); // ๋ณผ๋ฅจ์ 4๊ฐ ๋ณต์ฌ
int i = 0;
// 4๊ฐ์ฉ ์ฒ๋ฆฌ
for(; i <= numSamples - 4; i += 4) {
__m128 samples = _mm_loadu_ps(&buffer[i]);
samples = _mm_mul_ps(samples, volumeVec);
_mm_storeu_ps(&buffer[i], samples);
}
// ๋๋จธ์ง ์ฒ๋ฆฌ
for(; i < numSamples; i++) {
buffer[i] *= volume;
}
}
// ์ฑ๋ฅ ๋น๊ต
void benchmarkVolume() {
const int SIZE = 1000000;
std::vector<float> buffer1(SIZE, 1.0f);
std::vector<float> buffer2(SIZE, 1.0f);
// ์ค์นผ๋ผ ๋ฒ์
auto start = std::chrono::high_resolution_clock::now();
applyVolumeScalar(buffer1.data(), SIZE, 0.5f);
auto end = std::chrono::high_resolution_clock::now();
auto durationScalar = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
// SSE ๋ฒ์
start = std::chrono::high_resolution_clock::now();
applyVolumeSSE(buffer2.data(), SIZE, 0.5f);
end = std::chrono::high_resolution_clock::now();
auto durationSSE = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "=== ์ฑ๋ฅ ๋น๊ต (1,000,000 ์ํ) ===" << std::endl;
std::cout << "์ค์นผ๋ผ: " << durationScalar.count() << " ฮผs" << std::endl;
std::cout << "SSE: " << durationSSE.count() << " ฮผs" << std::endl;
std::cout << "์๋ ํฅ์: " << (float)durationScalar.count() / durationSSE.count()
<< "๋ฐฐ" << std::endl;
}
๐ ๋ฃฉ์ ํ ์ด๋ธ๋ก ์ผ๊ฐํจ์ ์ต์ ํ
์ฌ์ธํ๋ ์ฝ์ฌ์ธ ๊ฐ์ ์ผ๊ฐํจ์๋ ๊ณ์ฐ์ด ๋๋ ค. ํ์ง๋ง ์ค๋์ค์์๋ ๊ฐ์ ๊ฐ์ ๋ฐ๋ณตํด์ ๊ณ์ฐํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์. ๊ทธ๋ด ๋ ๋ฏธ๋ฆฌ ๊ณ์ฐํด๋ ํ ์ด๋ธ์ ์ฐ๋ฉด ํจ์ฌ ๋นจ๋ผ! ๐
class WavetableOscillator {
private:
std::vector<float> wavetable;
float phase;
float phaseIncrement;
int tableSize;
public:
WavetableOscillator(int size = 2048) : tableSize(size), phase(0.0f) {
wavetable.resize(tableSize);
// ์ฌ์ธํ ํ
์ด๋ธ ์์ฑ
for(int i = 0; i < tableSize; i++) {
wavetable[i] = std::sin(2.0 * M_PI * i / tableSize);
}
}
void setFrequency(float frequency, float sampleRate) {
phaseIncrement = (frequency * tableSize) / sampleRate;
}
float getNextSample() {
// ์ ํ ๋ณด๊ฐ์ผ๋ก ๋ถ๋๋ฌ์ด ์ถ๋ ฅ
int index = static_cast<int>(phase);
float frac = phase - index;
int nextIndex = (index + 1) % tableSize;
float sample = wavetable[index] * (1.0f - frac) +
wavetable[nextIndex] * frac;
phase += phaseIncrement;
if(phase >= tableSize) {
phase -= tableSize;
}
return sample;
}
void fillBuffer(float* buffer, int numSamples) {
for(int i = 0; i < numSamples; i++) {
buffer[i] = getNextSample();
}
}
};
// ์ฑ๋ฅ ๋น๊ต
void benchmarkOscillator() {
const int SIZE = 1000000;
std::vector<float> buffer(SIZE);
// ์ง์ ๊ณ์ฐ
auto start = std::chrono::high_resolution_clock::now();
float phase = 0.0f;
float increment = 440.0f / 44100.0f;
for(int i = 0; i < SIZE; i++) {
buffer[i] = std::sin(2.0 * M_PI * phase);
phase += increment;
if(phase >= 1.0f) phase -= 1.0f;
}
auto end = std::chrono::high_resolution_clock::now();
auto durationDirect = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
// ์จ์ด๋ธํ
์ด๋ธ
start = std::chrono::high_resolution_clock::now();
WavetableOscillator osc;
osc.setFrequency(440.0f, 44100.0f);
osc.fillBuffer(buffer.data(), SIZE);
end = std::chrono::high_resolution_clock::now();
auto durationTable = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "=== ์ค์ค๋ ์ดํฐ ์ฑ๋ฅ ๋น๊ต ===" << std::endl;
std::cout << "์ง์ ๊ณ์ฐ: " << durationDirect.count() << " ฮผs" << std::endl;
std::cout << "์จ์ด๋ธํ
์ด๋ธ: " << durationTable.count() << " ฮผs" << std::endl;
std::cout << "์๋ ํฅ์: " << (float)durationDirect.count() / durationTable.count()
<< "๋ฐฐ" << std::endl;
}
๐ธ ์ค์ ํ๋ก์ ํธ: ๊ฐ๋จํ ์ ๋์ฌ์ด์ ๋ง๋ค๊ธฐ
์, ์ด์ ๋ฐฐ์ด ๋ชจ๋ ๊ฑธ ์ข ํฉํด์ ์ค์ ๋ก ์ฐ์ฃผํ ์ ์๋ ์ ๋์ฌ์ด์ ๋ฅผ ๋ง๋ค์ด๋ณด์! MIDI ์ ๋ ฅ์ ๋ฐ์์ ์๋ฆฌ๋ฅผ ๋ด๋ ํด๋ฆฌํฌ๋(์ฌ๋ฌ ์์ ๋์์) ์ ๋์ฌ์ด์ ์ผ. ๐น
#include <vector>
#include <memory>
#include <cmath>
// ADSR ์๋ฒจ๋กํ (Attack, Decay, Sustain, Release)
class ADSREnvelope {
private:
enum class State { Idle, Attack, Decay, Sustain, Release };
State state;
float attackTime;
float decayTime;
float sustainLevel;
float releaseTime;
float sampleRate;
float currentLevel;
float releaseStartLevel;
int sampleCounter;
public:
ADSREnvelope(float sr) : sampleRate(sr), state(State::Idle),
currentLevel(0.0f), sampleCounter(0) {
attackTime = 0.01f; // 10ms
decayTime = 0.1f; // 100ms
sustainLevel = 0.7f; // 70%
releaseTime = 0.2f; // 200ms
}
void setADSR(float attack, float decay, float sustain, float release) {
attackTime = attack;
decayTime = decay;
sustainLevel = std::clamp(sustain, 0.0f, 1.0f);
releaseTime = release;
}
void noteOn() {
state = State::Attack;
sampleCounter = 0;
}
void noteOff() {
if(state != State::Idle) {
state = State::Release;
releaseStartLevel = currentLevel;
sampleCounter = 0;
}
}
float getNextValue() {
switch(state) {
case State::Idle:
currentLevel = 0.0f;
break;
case State::Attack: {
int attackSamples = attackTime * sampleRate;
if(sampleCounter < attackSamples) {
currentLevel = (float)sampleCounter / attackSamples;
sampleCounter++;
} else {
state = State::Decay;
sampleCounter = 0;
}
break;
}
case State::Decay: {
int decaySamples = decayTime * sampleRate;
if(sampleCounter < decaySamples) {
float t = (float)sampleCounter / decaySamples;
currentLevel = 1.0f - t * (1.0f - sustainLevel);
sampleCounter++;
} else {
state = State::Sustain;
currentLevel = sustainLevel;
}
break;
}
case State::Sustain:
currentLevel = sustainLevel;
break;
case State::Release: {
int releaseSamples = releaseTime * sampleRate;
if(sampleCounter < releaseSamples) {
float t = (float)sampleCounter / releaseSamples;
currentLevel = releaseStartLevel * (1.0f - t);
sampleCounter++;
} else {
state = State::Idle;
currentLevel = 0.0f;
}
break;
}
}
return currentLevel;
}
bool isActive() const {
return state != State::Idle;
}
};
// ์ ๋์ฌ์ด์ ๋ณด์ด์ค (ํ๋์ ์)
class SynthVoice {
private:
WavetableOscillator oscillator;
ADSREnvelope envelope;
int noteNumber;
bool active;
float sampleRate;
public:
SynthVoice(float sr) : oscillator(), envelope(sr),
noteNumber(-1), active(false), sampleRate(sr) {}
void noteOn(int note, float velocity) {
noteNumber = note;
active = true;
// MIDI ๋
ธํธ ๋ฒํธ๋ฅผ ์ฃผํ์๋ก ๋ณํ
float frequency = 440.0f * std::pow(2.0f, (note - 69) / 12.0f);
oscillator.setFrequency(frequency, sampleRate);
envelope.noteOn();
}
void noteOff() {
envelope.noteOff();
}
float getNextSample() {
if(!active) return 0.0f;
float sample = oscillator.getNextSample();
float envValue = envelope.getNextValue();
if(!envelope.isActive()) {
active = false;
}
return sample * envValue;
}
bool isActive() const { return active; }
int getNoteNumber() const { return noteNumber; }
};
// ํด๋ฆฌํฌ๋ ์ ๋์ฌ์ด์
class PolyphonicSynth {
private:
std::vector<std::unique_ptr<SynthVoice>> voices;
int maxPolyphony;
float sampleRate;
public:
PolyphonicSynth(int polyphony, float sr)
: maxPolyphony(polyphony), sampleRate(sr) {
for(int i = 0; i < maxPolyphony; i++) {
voices.push_back(std::make_unique<SynthVoice>(sampleRate));
}
}
void noteOn(int noteNumber, float velocity = 1.0f) {
// ๋นํ์ฑ ๋ณด์ด์ค ์ฐพ๊ธฐ
for(auto& voice : voices) {
if(!voice->isActive()) {
voice->noteOn(noteNumber, velocity);
return;
}
}
// ๋ชจ๋ ๋ณด์ด์ค๊ฐ ์ฌ์ฉ ์ค์ด๋ฉด ๊ฐ์ฅ ์ค๋๋ ๊ฒ ๊ต์ฒด
voices[0]->noteOn(noteNumber, velocity);
}
void noteOff(int noteNumber) {
for(auto& voice : voices) {
if(voice->isActive() && voice->getNoteNumber() == noteNumber) {
voice->noteOff();
}
}
}
void allNotesOff() {
for(auto& voice : voices) {
if(voice->isActive()) {
voice->noteOff();
}
}
}
float getNextSample() {
float output = 0.0f;
for(auto& voice : voices) {
output += voice->getNextSample();
}
// ํด๋ฆฌํฌ๋ ์๋ก ๋๋ ์ ํด๋ฆฌํ ๋ฐฉ์ง
return output / maxPolyphony;
}
void fillBuffer(float* buffer, int numSamples) {
for(int i = 0; i < numSamples; i++) {
buffer[i] = getNextSample();
}
}
};
// ์ฌ์ฉ ์์
int main() {
const int SAMPLE_RATE = 44100;
const int POLYPHONY = 8; // ์ต๋ 8๊ฐ ์ ๋์ ๋ฐ์
PolyphonicSynth synth(POLYPHONY, SAMPLE_RATE);
// C ๋ฉ์ด์ ์ฝ๋ ์ฐ์ฃผ (C, E, G)
synth.noteOn(60, 1.0f); // C4
synth.noteOn(64, 1.0f); // E4
synth.noteOn(67, 1.0f); // G4
// 1์ด๊ฐ ์ค๋์ค ์์ฑ
std::vector<float> audioBuffer(SAMPLE_RATE);
synth.fillBuffer(audioBuffer.data(), SAMPLE_RATE);
// 0.5์ด ํ ๋
ธํธ ์คํ
synth.noteOff(60);
synth.noteOff(64);
synth.noteOff(67);
// ๋๋จธ์ง 0.5์ด (๋ฆด๋ฆฌ์ฆ)
std::vector<float> releaseBuffer(SAMPLE_RATE / 2);
synth.fillBuffer(releaseBuffer.data(), SAMPLE_RATE / 2);
std::cout << "์ ๋์ฌ์ด์ ์ฐ์ฃผ ์๋ฃ!" << std::endl;
std::cout << "C ๋ฉ์ด์ ์ฝ๋๊ฐ ์์ฑ๋์์ต๋๋ค." << std::endl;
return 0;
}
์ค์ค๋ ์ดํฐ: ๊ธฐ๋ณธ ํํ ์์ฑ (์ฌ์ธํ, ํฑ๋ํ, ์ฌ๊ฐํ ๋ฑ)
์๋ฒจ๋กํ: ์๊ฐ์ ๋ฐ๋ฅธ ์๋ ๋ณํ (ADSR)
ํํฐ: ์์ ์กฐ์ (๋ก์ฐํจ์ค, ํ์ดํจ์ค ๋ฑ)
LFO: ์ ์ฃผํ ์ค์ค๋ ์ดํฐ๋ก ๋น๋ธ๋ผํ , ํธ๋ ๋ชฐ๋ก ํจ๊ณผ
์ดํํธ: ๋๋ ์ด, ๋ฆฌ๋ฒ๋ธ, ์ฝ๋ฌ์ค ๋ฑ
๐ ํฌ๋ก์ค ํ๋ซํผ ๊ฐ๋ฐ๊ณผ ๋ฐฐํฌ
์ค๋์ค ํ๋ก๊ทธ๋จ์ ๋ง๋ค์์ผ๋ฉด ์ฌ๋ฌ ํ๋ซํผ์์ ๋์ํ๊ฒ ๋ง๋ค๊ณ ์ถ๊ฒ ์ง? Windows, macOS, Linux, ์ฌ์ง์ด ๋ชจ๋ฐ์ผ๊น์ง! ๊ฐ ํ๋ซํผ์ ํน์ฑ์ ์์๋ณด์. ๐ฅ๏ธ๐ฑ
Windows: WASAPI (์ ์ง์ฐ) vs DirectSound (ํธํ์ฑ)
macOS: Core Audio ํ์, ์๋๋ฐ์ค ๊ถํ ์ค์
Linux: ALSA, PulseAudio, JACK ๋ฑ ๋ค์ํ ๋ฐฑ์๋
iOS: Audio Unit, ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋์ค ๊ถํ
Android: OpenSL ES, AAudio (Android 8.0+)
๐ง CMake๋ฅผ ํ์ฉํ ํฌ๋ก์ค ํ๋ซํผ ๋น๋
# CMakeLists.txt ์์
cmake_minimum_required(VERSION 3.15)
project(AudioApp VERSION 1.0.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# ์์ค ํ์ผ
set(SOURCES
src/main.cpp
src/audio_engine.cpp
src/synth.cpp
src/effects.cpp
)
# ์คํ ํ์ผ ์์ฑ
add_executable(AudioApp ${SOURCES})
# PortAudio ์ฐพ๊ธฐ
find_package(portaudio REQUIRED)
target_link_libraries(AudioApp PRIVATE portaudio)
# ํ๋ซํผ๋ณ ์ค์
if(WIN32)
# Windows ํน์ ์ค์
target_compile_definitions(AudioApp PRIVATE PLATFORM_WINDOWS)
target_link_libraries(AudioApp PRIVATE winmm)
elseif(APPLE)
# macOS ํน์ ์ค์
target_compile_definitions(AudioApp PRIVATE PLATFORM_MACOS)
find_library(COREAUDIO_LIBRARY CoreAudio)
find_library(COREFOUNDATION_LIBRARY CoreFoundation)
target_link_libraries(AudioApp PRIVATE
${COREAUDIO_LIBRARY}
${COREFOUNDATION_LIBRARY}
)
elseif(UNIX)
# Linux ํน์ ์ค์
target_compile_definitions(AudioApp PRIVATE PLATFORM_LINUX)
target_link_libraries(AudioApp PRIVATE pthread asound)
endif()
# ์ต์ ํ ํ๋๊ทธ
if(CMAKE_BUILD_TYPE STREQUAL "Release")
if(MSVC)
target_compile_options(AudioApp PRIVATE /O2 /arch:AVX2)
else()
target_compile_options(AudioApp PRIVATE -O3 -march=native)
endif()
endif()
๐ ๋ ๋์๊ฐ๊ธฐ: ๊ณ ๊ธ ์ฃผ์ ๋ค
๊ธฐ๋ณธ์ ๋ง์คํฐํ๋ค๋ฉด ์ด์ ๋ ๊น์ด ๋ค์ด๊ฐ ์ฐจ๋ก์ผ! ์ฌ๊ธฐ ๋ช ๊ฐ์ง ๊ณ ๊ธ ์ฃผ์ ๋ฅผ ์๊ฐํ ๊ฒ. ๊ฐ๊ฐ ํ๋์ฉ๋ง ํ๋ ๋ช ๋ฌ์ ๊ฑธ๋ฆฌ๋ ๊น์ ์ฃผ์ ๋ค์ด์ผ. ๐
| ์ฃผ์ | ์ค๋ช | ํ์ฉ ๋ถ์ผ |
|---|---|---|
| FFT/์คํํธ๋ผ ๋ถ์ | ์ฃผํ์ ์์ญ์์ ์ ํธ ๋ถ์ | ์ดํ๋ผ์ด์ , ๋ณด์ฝ๋, ํผ์น ๊ฒ์ถ |
| ์ปจ๋ณผ๋ฃจ์ ๋ฆฌ๋ฒ๋ธ | ์ค์ ๊ณต๊ฐ์ ์ํ์ค ์๋ต ์ฌ์ฉ | ํ์ค์ ์ธ ๊ณต๊ฐ ์๋ฎฌ๋ ์ด์ |
| ๋ฌผ๋ฆฌ ๋ชจ๋ธ๋ง | ์ ๊ธฐ์ ๋ฌผ๋ฆฌ์ ํน์ฑ ์๋ฎฌ๋ ์ด์ | ํ์ ๊ธฐ, ๊ด์ ๊ธฐ ์ ๋์ฌ์ด์ |
| ๋จธ์ ๋ฌ๋ ์ค๋์ค | AI๋ฅผ ํ์ฉํ ์ค๋์ค ์ฒ๋ฆฌ | ๋ ธ์ด์ฆ ์ ๊ฑฐ, ์์ ๋ถ๋ฆฌ, ์์ฑ |
| ๊ณต๊ฐ ์ค๋์ค | 3D ์ฌ์ด๋, ๋ฐ์ด๋ ธ๋ด | VR/AR, ๊ฒ์, ์ํ |
๐ฌ FFT๋ฅผ ํ์ฉํ ์คํํธ๋ผ ๋ถ์ ๋ง๋ณด๊ธฐ
FFT(Fast Fourier Transform)๋ ์๊ฐ ์์ญ์ ์ ํธ๋ฅผ ์ฃผํ์ ์์ญ์ผ๋ก ๋ณํํด์ค. ์ด๊ฑธ๋ก ์ด๋ค ์ฃผํ์๊ฐ ์ผ๋ง๋ ํฌํจ๋์ด ์๋์ง ์ ์ ์์ง! ๐
// FFTW ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ ์์
#include <fftw3.h>
#include <vector>
#include <cmath>
class SpectrumAnalyzer {
private:
int fftSize;
fftwf_plan plan;
float* inputBuffer;
fftwf_complex* outputBuffer;
std::vector<float> magnitudes;
std::vector<float> window;
void createHannWindow() {
window.resize(fftSize);
for(int i = 0; i < fftSize; i++) {
window[i] = 0.5f * (1.0f - std::cos(2.0 * M_PI * i / (fftSize - 1)));
}
}
public:
SpectrumAnalyzer(int size = 2048) : fftSize(size) {
inputBuffer = fftwf_alloc_real(fftSize);
outputBuffer = fftwf_alloc_complex(fftSize / 2 + 1);
plan = fftwf_plan_dft_r2c_1d(fftSize, inputBuffer, outputBuffer, FFTW_ESTIMATE);
magnitudes.resize(fftSize / 2 + 1);
createHannWindow();
}
~SpectrumAnalyzer() {
fftwf_destroy_plan(plan);
fftwf_free(inputBuffer);
fftwf_free(outputBuffer);
}
void analyze(const float* audioData) {
// ์๋์ฐ ํจ์ ์ ์ฉ
for(int i = 0; i < fftSize; i++) {
inputBuffer[i] = audioData[i] * window[i];
}
// FFT ์คํ
fftwf_execute(plan);
// ํฌ๊ธฐ ๊ณ์ฐ
for(int i = 0; i < fftSize / 2 + 1; i++) {
float real = outputBuffer[i][0];
float imag = outputBuffer[i][1];
magnitudes[i] = std::sqrt(real * real + imag * imag) / fftSize;
}
}
float getMagnitudeAt(int bin) const {
return magnitudes[bin];
}
float getMagnitudeAtFrequency(float frequency, float sampleRate) const {
int bin = (frequency * fftSize) / sampleRate;
return getMagnitudeAt(bin);
}
const std::vector<float>& getMagnitudes() const {
return magnitudes;
}
};
๐ผ ์ค๋ฌด์์์ ์ค๋์ค ํ๋ก๊ทธ๋๋ฐ
์ด์ ์ค๋ฌด ์ด์ผ๊ธฐ๋ฅผ ํด๋ณผ๊น? ์ค๋์ค ํ๋ก๊ทธ๋๋ฐ์ ์ ๋ง ๋ค์ํ ๋ถ์ผ์์ ์ฐ์ฌ. ๊ฒ์, ์์ ์ ์, ๋ฐฉ์ก, ํต์ , ์๋ฃ, ์ฌ์ง์ด ์๋์ฐจ๊น์ง! ๐๐ต
๊ฒ์ ๊ฐ๋ฐ: ์ค์๊ฐ 3D ์ค๋์ค, ๋์ ์์ , ์ฌ์ด๋ ์ดํํธ
DAW/ํ๋ฌ๊ทธ์ธ: VST, AU, AAX ํ๋ฌ๊ทธ์ธ ๊ฐ๋ฐ
์คํธ๋ฆฌ๋ฐ: ์ค์๊ฐ ์ธ์ฝ๋ฉ/๋์ฝ๋ฉ, ์ ์ง์ฐ ์ ์ก
์์ฑ ํต์ : VoIP, ๋ ธ์ด์ฆ ์บ์ฌ๋ง, ์์ฝ ์ ๊ฑฐ
์ค๋์ค ๋ณต์: ๋ ธ์ด์ฆ ์ ๊ฑฐ, ์ ์ค์ผ์ผ๋ง, ๋ง์คํฐ๋ง
์๋ฒ ๋๋: IoT ๋๋ฐ์ด์ค, ์ค๋งํธ ์คํผ์ปค, ๋ณด์ฒญ๊ธฐ
ํนํ ์์ฆ์ ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์ค๋์ค ํ๋ก๊ทธ๋๋ฐ ๊ด๋ จ ํ๋ฆฌ๋์ค ํ๋ก์ ํธ๋ ๋ง์ด ์ฌ๋ผ์. VST ํ๋ฌ๊ทธ์ธ ๊ฐ๋ฐ, ์ค๋์ค ์ฒ๋ฆฌ ์๊ณ ๋ฆฌ์ฆ ๊ตฌํ, ์์ ์ฑ ๊ฐ๋ฐ ๊ฐ์ ๊ฑฐ ๋ง์ด์ผ. C++ ์ค๋์ค ํ๋ก๊ทธ๋๋ฐ ์ค๋ ฅ์ด ์์ผ๋ฉด ์ ๋ง ๋ค์ํ ๊ธฐํ๊ฐ ์์ด! ๐ผ
๐ธ VST ํ๋ฌ๊ทธ์ธ ๊ฐ๋ฐ ์ ๋ฌธ
VST(Virtual Studio Technology)๋ ๊ฐ์ฅ ๋๋ฆฌ ์ฌ์ฉ๋๋ ์ค๋์ค ํ๋ฌ๊ทธ์ธ ํฌ๋งท์ด์ผ. JUCE ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๋ฉด ๋น๊ต์ ์ฝ๊ฒ ๋ง๋ค ์ ์์ด.
// JUCE๋ฅผ ์ฌ์ฉํ ๊ฐ๋จํ ๊ฒ์ธ ํ๋ฌ๊ทธ์ธ ์์
#include <JuceHeader.h>
class SimpleGainProcessor : public juce::AudioProcessor {
private:
juce::AudioParameterFloat* gainParameter;
public:
SimpleGainProcessor() {
addParameter(gainParameter = new juce::AudioParameterFloat(
"gain", // ํ๋ผ๋ฏธํฐ ID
"Gain", // ํ๋ผ๋ฏธํฐ ์ด๋ฆ
-60.0f, // ์ต์๊ฐ (dB)
12.0f, // ์ต๋๊ฐ (dB)
0.0f // ๊ธฐ๋ณธ๊ฐ (dB)
));
}
void prepareToPlay(double sampleRate, int samplesPerBlock) override {
// ์ด๊ธฐํ ์์
}
void processBlock(juce::AudioBuffer<float>& buffer,
juce::MidiBuffer& midiMessages) override {
// ๊ฒ์ธ์ dB์์ ์ ํ์ผ๋ก ๋ณํ
float gainLinear = juce::Decibels::decibelsToGain(gainParameter->get());
// ๋ชจ๋ ์ฑ๋์ ๊ฒ์ธ ์ ์ฉ
for(int channel = 0; channel < buffer.getNumChannels(); ++channel) {
float* channelData = buffer.getWritePointer(channel);
for(int sample = 0; sample < buffer.getNumSamples(); ++sample) {
channelData[sample] *= gainLinear;
}
}
}
// ํ์ ์ค๋ฒ๋ผ์ด๋ ํจ์๋ค
const juce::String getName() const override { return "Simple Gain"; }
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
double getTailLengthSeconds() const override { return 0.0; }
// ... ๊ธฐํ ํ์ ํจ์๋ค
};
๐ ๋๋ฒ๊น ๊ณผ ํ ์คํ
์ค๋์ค ํ๋ก๊ทธ๋๋ฐ์์ ๋ฒ๊ทธ๋ ์ ๋ง ์ฐพ๊ธฐ ์ด๋ ค์. ์๋ฆฌ๋ก๋ง ํ์ธํด์ผ ํ๋๊น! ์ฌ๊ธฐ ๋ช ๊ฐ์ง ๋๋ฒ๊น ํ์ ์๋ ค์ค๊ฒ. ๐
1. ์๊ฐํ: ํํ์ ๊ทธ๋ํ๋ก ๊ทธ๋ ค์ ํ์ธ
2. ๋จ์ ํ ์คํธ: ๊ฐ DSP ๋ชจ๋์ ๋ ๋ฆฝ์ ์ผ๋ก ํ ์คํธ
3. ์คํ๋ผ์ธ ์ฒ๋ฆฌ: ์ค์๊ฐ ๋์ ํ์ผ๋ก ์ฒ๋ฆฌํด์ ๋๋ฒ๊น
4. ์ด์ค์ : ์ํ ๊ฐ์ด ๋ฒ์๋ฅผ ๋ฒ์ด๋๋์ง ์ฒดํฌ
5. ํ๋กํ์ผ๋ง: CPU ์ฌ์ฉ๋๊ณผ ๋ ์ดํด์ ์ธก์
// ์ค๋์ค ๋๋ฒ๊น
์ ํธ๋ฆฌํฐ
class AudioDebugger {
public:
// ๋ฒํผ ๋ด์ฉ์ CSV ํ์ผ๋ก ์ ์ฅ
static void saveBufferToCSV(const float* buffer, int numSamples,
const std::string& filename) {
std::ofstream file(filename);
file << "Sample,Value\n";
for(int i = 0; i < numSamples; i++) {
file << i << "," << buffer[i] << "\n";
}
file.close();
std::cout << "๋ฒํผ๋ฅผ " << filename << "์ ์ ์ฅํ์ต๋๋ค." << std::endl;
}
// ๋ฒํผ ํต๊ณ ์ถ๋ ฅ
static void printBufferStats(const float* buffer, int numSamples) {
float min = buffer[0], max = buffer[0], sum = 0.0f;
int clippedSamples = 0;
for(int i = 0; i < numSamples; i++) {
float sample = buffer[i];
min = std::min(min, sample);
max = std::max(max, sample);
sum += std::abs(sample);
if(std::abs(sample) > 1.0f) {
clippedSamples++;
}
}
float average = sum / numSamples;
std::cout << "=== ๋ฒํผ ํต๊ณ ===" << std::endl;
std::cout << "์ต์๊ฐ: " << min << std::endl;
std::cout << "์ต๋๊ฐ: " << max << std::endl;
std::cout << "ํ๊ท ์ ๋๊ฐ: " << average << std::endl;
std::cout << "ํด๋ฆฌํ๋ ์ํ: " << clippedSamples << " / " << numSamples;
if(clippedSamples > 0) {
std::cout << " โ ๏ธ ํด๋ฆฌํ ๋ฐ์!" << std::endl;
} else {
std::cout << " โ
์ ์" << std::endl;
}
}
// NaN์ด๋ Inf ์ฒดํฌ
static bool checkForInvalidSamples(const float* buffer, int numSamples) {
for(int i = 0; i < numSamples; i++) {
if(std::isnan(buffer[i]) || std::isinf(buffer[i])) {
std::cerr << "โ ๏ธ ์ํ " << i << "์์ ์ ํจํ์ง ์์ ๊ฐ ๋ฐ๊ฒฌ: "
<< buffer[i] << std::endl;
return true;
}
}
return false;
}
};
๐ ํ์ต ๋ฆฌ์์ค์ ์ปค๋ฎค๋ํฐ
์ค๋์ค ํ๋ก๊ทธ๋๋ฐ์ ๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด? ์ฌ๊ธฐ ์ถ์ฒ ๋ฆฌ์์ค๋ค์ ์ ๋ฆฌํด๋ดค์ด! ๐
โข "The Audio Programming Book" - Richard Boulanger
โข "Designing Audio Effect Plugins in C++" - Will Pirkle
โข "DAFX: Digital Audio Effects" - Udo Zรถlzer
โข "Designing Sound" - Andy Farnell
๐ ์จ๋ผ์ธ ๋ฆฌ์์ค
โข Music DSP (musicdsp.org) - DSP ์๊ณ ๋ฆฌ์ฆ ์์นด์ด๋ธ
โข KVR Audio - ํ๋ฌ๊ทธ์ธ ๊ฐ๋ฐ ํฌ๋ผ
โข JUCE Forum - JUCE ํ๋ ์์ํฌ ์ปค๋ฎค๋ํฐ
โข The Audio Programmer (YouTube) - ํํ ๋ฆฌ์ผ ์ฑ๋
๐ ์จ๋ผ์ธ ๊ฐ์
โข Coursera: Audio Signal Processing for Music Applications
โข Kadenze: Audio Plugin Development
โข YouTube: The Audio Programmer ์ฑ๋
๊ทธ๋ฆฌ๊ณ ์ฌ๋ฅ๋ท์์๋ ์ค๋์ค ํ๋ก๊ทธ๋๋ฐ ๊ด๋ จ ๋ฉํ ๋ง์ด๋ ๊ฐ์๋ฅผ ์ฐพ์๋ณผ ์ ์์ด. ์ค๋ฌด ๊ฒฝํ์ด ์๋ ๊ฐ๋ฐ์๋ค์๊ฒ ์ง์ ๋ฐฐ์ธ ์ ์๋ ์ข์ ๊ธฐํ์ง! ๐
๐ฌ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์, ์ ๋ง ๊ธด ์ฌ์ ์ด์์ง? ๐ ์ฐ๋ฆฌ๋ ์ค๋์ค์ ๊ธฐ์ด๋ถํฐ ์์ํด์ ์ค์๊ฐ ์ฒ๋ฆฌ, DSP, ์ ๋์ฌ์ด์ ๊ตฌํ, ๊ทธ๋ฆฌ๊ณ ์ค๋ฌด ํ์ฉ๊น์ง ์ ๋ง ๋ง์ ๊ฑธ ๋ค๋ค์ด!
์ค๋์ค ํ๋ก๊ทธ๋๋ฐ์ ์ฒ์์ ์ด๋ ต๊ฒ ๋๊ปด์ง ์ ์์ด. ์ํ๋ ๋์ค๊ณ , ๊ฐ๋
๋ ์ถ์์ ์ด๊ณ , ๋ฒ๊ทธ๋ ์ฐพ๊ธฐ ํ๋ค๊ฑฐ๋ . ํ์ง๋ง ํ ๋ฒ ์ต์ํด์ง๋ฉด ์ ๋ง ์ฌ๋ฏธ์์ด! ๋ด๊ฐ ๋ง๋ ์ฝ๋๊ฐ ์ค์ ๋ก ์๋ฆฌ๋ก ๋ค๋ฆฐ๋ค๋ ๊ฒ ์ผ๋ง๋ ์ ๊ธฐํ์ง ๋ชฐ๋ผ. ๐ต
์ค์ํ ๊ฑด ๊พธ์คํ ์ค์ต์ด์ผ. ์ด๋ก ๋ง ์ฝ์ง ๋ง๊ณ ์ง์ ์ฝ๋๋ฅผ ์์ฑํด๋ด. ๊ฐ๋จํ ์ฌ์ธํ ์์ฑ๋ถํฐ ์์ํด์, ํํฐ ๋ง๋ค๊ณ , ์ดํํธ ์ถ๊ฐํ๊ณ ... ํ๋์ฉ ์์๊ฐ๋ค ๋ณด๋ฉด ์ด๋์ ๋ฉ์ง ์ค๋์ค ์ ํ๋ฆฌ์ผ์ด์
์ ๋ง๋ค๊ณ ์์ ๊ฑฐ์ผ! ๐ช
1. ์ค๋ ๋ฐฐ์ด ์ฝ๋๋ฅผ ์ง์ ์คํํด๋ณด๊ธฐ
2. ๊ฐ๋จํ ์ดํํธ ํ๋ฌ๊ทธ์ธ ๋ง๋ค์ด๋ณด๊ธฐ
3. ์คํ์์ค ์ค๋์ค ํ๋ก์ ํธ์ ๊ธฐ์ฌํ๊ธฐ
4. ์์ ๋ง์ ์ ๋์ฌ์ด์ ๋์์ธํ๊ธฐ
5. ์ฌ๋ฅ๋ท์์ ์๊ท๋ชจ ํ๋ก์ ํธ ๋์ ํด๋ณด๊ธฐ
์ค๋์ค ํ๋ก๊ทธ๋๋ฐ์ ์์
, ๊ฒ์, ์ํ, ํต์ ๋ฑ ์ ๋ง ๋ค์ํ ๋ถ์ผ์์ ํ์๋ก ํ๋ ๊ธฐ์ ์ด์ผ. C++๋ก ์ค๋์ค๋ฅผ ๋ค๋ฃฐ ์ ์๋ค๋ฉด ์ ๋ง ๋ง์ ๊ธฐํ๊ฐ ์ด๋ ค ์์ด. ํนํ VST ํ๋ฌ๊ทธ์ธ ๊ฐ๋ฐ์ด๋ ๊ฒ์ ์ค๋์ค ์์ง ๊ฐ์ ๋ถ์ผ๋ ์์๋ ๋ง๊ณ ๋ณด์๋ ์ข์ ํธ์ด์ง! ๐ผ
ํน์ ๋งํ๋ ๋ถ๋ถ์ด ์๊ฑฐ๋ ๋ ๊น์ด ๋ฐฐ์ฐ๊ณ ์ถ๋ค๋ฉด, ์จ๋ผ์ธ ์ปค๋ฎค๋ํฐ๋ ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์ ํ์ฉํด๋ด. ๊ฒฝํ ๋ง์ ๊ฐ๋ฐ์๋ค์๊ฒ ๋ฉํ ๋ง์ ๋ฐ๋ ๊ฒ๋ ์ ๋ง ํฐ ๋์์ด ๋ผ!
์, ์ด์ ํค๋ํฐ์ ๋ผ๊ณ ์ฝ๋ ์๋ํฐ๋ฅผ ์ด์ด๋ด. ๋น์ ๋ง์ ์ค๋์ค ๋ง๋ฒ์ ๋ง๋ค์ด๋ผ ์๊ฐ์ด์ผ! ๐งโจ
Happy Coding! ๊ทธ๋ฆฌ๊ณ Happy Listening! ๐ต
๐ต ์ค๋์ค ํ๋ก๊ทธ๋๋ฐ์ ์ธ๊ณ์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค!
์๋ฆฌ๋ฅผ ์ฝ๋๋ก, ์ฝ๋๋ฅผ ์์ ์ผ๋ก - ๋น์ ์ ์ฐฝ์๋ ฅ์ ๋ ๊ฐ๋ฅผ ๋ฌ์์ฃผ์ธ์! ๐
๊ด๋ จ ํค์๋
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.

๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.