Oscillator

Mechboy·2025년 7월 9일

DSP

목록 보기
1/6

Oscillator

1. 개요

  • Oscillator는 LC회로에서 공진현상을 이용해서 파형을 만드는 회로를 의미함
  • 신디사이저에서 오실레이터는 음을 재생시키기 위한 파형을 생성한다고 보면 됨
  • 대표적으로 Sine, Square, triangle, Saw 등을 활용해서 생성함

2. 파형 타입

  1. Sine wave

    • Sine wave는 기본적인 정현 파를 의미함
    • fourier 시리즈를 통해서 확인 해보면 순수한 정현파만 있음, 즉 배음이 없고 해당 주파수에 대한 사운드만 있음
  2. Square wave

    • Max와 min의 지속시간이 동일한 파형이라고 보면됨
    • 배수는 홀수배 만큼 생성됨
    • 1(2n1)\frac{1}{(2n-1)} 만큼 배음이 감소하기 삼각파 보다는 배음이 강조 되는 느낌임
  3. Triangle wave

    • Max와 min사이를 linear하게 변하는 파형을 의미함
    • 배수는 홀수배 만큼 생성됨
    • 1(2n1)2\frac{1}{(2n-1)^{2}} 만큼 배음이 감소하기 때문에 square 웨이브보다 배음의 크기가 작게 들림
  4. Sawtooth wave

    • 주기의 시작점에서 서서히 증가하여 끝까지 도달하는 파형임, 생긴게 톱날처럼 생겨서 톱니파라고 부름

    • 모든 배수에서도 배음이 존재하기 때문에 풍부한 사운드가 특징임, 사각파의 경우에는 1n\frac{1}{n} 의 배음의 크기를 가지기 떄문에 배음이 큰편임.

      수식 출처

3. Juce에서 발진 방법

  1. WaveTable 방식
  • 기본적으로 JUCE의 발진방법은 Wavetable 방식으로 미리 파형의 값을 계산하여 저장해둔뒤 파형에 맞춰서 샘플링을 하는 방식으로 활용함
// supersaw wave 생성예제
    osc.initialise([](float x) {return std::sin(x); }, 128);
    for (int i = 0; i < 7; ++i)
    {
        subOscl[i].initialise([](float x)
            {
                // 기본 톱니파(sawtooth) 람다
                return x / juce::MathConstants<float>::pi;
            },
            128);  // 초기 해상도 설정 (테스트용), 이후 prepareSuperSaw에서 재초기화
    }
  • JUCE에서는 파형을 초기화할때 Lambda함수와 웨이브테이블의 길이를 지정하여 자동으로 연산을 하도록 지정 되어있음, 아래식을 보면 각 아날로그 파형에 대한 식을 구현한 것임
/*Sine wave */
        osc.initialise(
            [](float x) { return std::sin(x); },
            tableResolution);
/*Square wave */
        osc.initialise(
            [](float x) {return x < 0.0f ? -1.0f : +1.0f;},
            tableResolution);
/*Triangle wave */
        osc.initialise(
            [](float x)
            {
                return std::asin(std::sin(x)) * (2.0f / juce::MathConstants<float>::pi);
            },
            tableResolution);
/*Sawtooth wave */
        osc.initialise(
            [](float x)
            {
                return (x / juce::MathConstants<float>::pi);
            },
            tableResolution);

4. Juce에서 설정방법

1. 대략적인 적용 순서

//1. Oscilator 선언
juce::dsp::Oscillator<float> osc;

void Oscillator::setSampleRate(double input_rate){
    sampleRate = input_rate;

//2. Sampling rate 설정
juce::dsp::ProcessSpec spec;
spec.sampleRate = sampleRate;
spec.maximumBlockSize = 512;  //오디오 버퍼 사이즈 설정
spec.numChannels = 1; //오디오 채널 설정        
osc.prepare(spec);

//3. 주파수 설정
osc.setFrequency(freq);

//4. 오디오 처리 구현 
void Oscillator::getNextSample(juce::AudioBuffer<float>& buffer, int startSample, int numSamples)
{
    for (int s = 0; s < numSamples; ++s)
    {
        float outSample = 0.0f;

        if (currentWaveType == WaveType::SuperSaw)
            outSample = SuperSaw();
        else
            outSample = osc.processSample(0.0f);

        for (int ch = 0; ch < buffer.getNumChannels(); ++ch)
            buffer.addSample(ch, startSample + s, outSample);
    }
}
  • 대략적으로 위의 순서대로 이루어 진다고 보면됨 여기서 중요한점이 있다면 JUCE에서는 버퍼단위로 오디오 신호처리가 이루어 지고있음

2. Wavetable 방식

  • 대략적으로 웨이브 테이블의 방식은 샘플링레이트가 고정된 상황에서 주파스에 의해 변하게 되는 위상변화량을 계산해서 -> 해당 위치에 맞는 인덱스를 불러온다고 보면됨.
  • 대략적으로 웨이브테이블의 사이즈가 128이라고 하면 의 샘플간 위상차에 대한 공식은

    PeriodWavetableSize=2π/128=0.049[rad]\frac{Period}{WavetableSize} = 2\pi / 128 = 0.049[rad]

    으로 계산이 가능함 즉 샘플하나당 간격이 0.05rad 정도 차이가 난다고 보면됨, 여기서 저 파형간의 샘플링간격이 짧을수록 소리 고주파수에서도 손실없이 대응이 가능

  • 최종적인 순서는 아래 과정을 계속 반복하여 버퍼에 넣어주면됨

// 1. 현재 위상을 기준으로 인덱스값 얻기
output[i] = interpolateWavetable(currentPhase);

// 2. 주파수에 따른 Phase increment 값 합산
currentPhase += phase_increment;

// 3. 주파수에 따른 Phase increment 값 합산
if (currentPhase >= 2pi) 
    currentPhase = 0;
profile
imageprocessing and Data science

0개의 댓글