频率调制合成算法

8

根据我的阅读,我已经为FM音频合成编写了一个算法。我不确定是否做得正确。创建软件合成器乐器时,使用函数生成振荡器,并可以使用调制器来调制此振荡器的频率。我不知道FM合成是否只适用于调制正弦波?

该算法采用乐器波形函数和频率调制器的调制指数和比率。对于每个音符,它获取频率并存储载波和调制器振荡器的相位值。调制器始终使用正弦波。

这是伪代码中的算法:

function ProduceSample(instrument, notes_playing)
    for each note in notes_playing
        if note.isPlaying()
            # Calculate signal
            if instrument.FMIndex != 0 # Apply FM
                FMFrequency = note.frequency*instrument.FMRatio; # FM frequency is factor of note frequency.
                note.FMPhase = note.FMPhase + FMFrequency / kGraphSampleRate # Phase of modulator.
                frequencyDeviation = sin(note.FMPhase * PI)*instrument.FMIndex*FMFrequency # Frequency deviation. Max deviation is a factor of the FM frequency. Modulation is done by a sine wave. 
                note.phase = note.phase + (note.frequency + frequencyDeviation) / kGraphSampleRate # Adjust phase with deviation
                # Reset the phase value to prevent the float from overflowing
                if note.FMPhase >= 1
                    note.FMPhase = note.FMPhase - 1
                end if
            else # No FM applied
                note.phase = note.phase + note.frequency / kGraphSampleRate # Adjust phase without deviation
            end if
            # Calculate the next sample
            signal = signal + instrument.waveFunction(note.phase,instrument.waveParameter)*note.amplitude
            # Reset the phase value to prevent the float from overflowing
            if note.phase >= 1
                note.phase = note.phase - 1
            end if
        end if
    end loop
    return signal
end function 

如果音符的频率为100Hz,FMRatio设置为0.5,FMIndex为0.1,则应在50Hz周期内产生95Hz到105Hz之间的频率。这种方式是否正确?我的测试表明,它听起来并不总是正确,特别是在调制锯齿波和方波时。是否可以像这样调制锯齿波和方波,或者只适用于正弦波?
以下是C和CoreAudio中的实现:
static OSStatus renderInput(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){
    AudioSynthesiser * audioController = (AudioSynthesiser *)inRefCon;
    // Get a pointer to the dataBuffer of the AudioBufferList
    AudioSampleType * outA = (AudioSampleType *) ioData->mBuffers[0].mData;
    if(!audioController->playing){
        for (UInt32 i = 0; i < inNumberFrames; ++i){
            outA[i] = (SInt16)0;
        }
        return noErr;
    }
    Track * track = &audioController->tracks[inBusNumber];
    SynthInstrument * instrument = (SynthInstrument *)track;
    float frequency_deviation;
    float FMFrequency;
    // Loop through the callback buffer, generating samples
    for (UInt32 i = 0; i < inNumberFrames; ++i){
        float signal = 0;
        for (int x = 0; x < 10; x++) {
            Note * note = track->notes_playing[x];
            if(note){
                //Envelope code removed
                //Calculate signal
                if (instrument->FMIndex) { //Apply FM
                    FMFrequency = note->frequency*instrument->FMRatio; //FM frequency is factor of note frequency.
                    note->FMPhase += FMFrequency / kGraphSampleRate; //Phase of modulator.
                    frequency_deviation = sinf(note->FMPhase * M_PI)*instrument->FMIndex*FMFrequency; //Frequency deviation. Max deviation is a factor of the FM frequency. Modulation is done by a sine wave. 
                    note->phase += (note->frequency + frequency_deviation) / kGraphSampleRate; //Adjust phase with deviation
                    // Reset the phase value to prevent the float from overflowing
                    if (note->FMPhase >= 1){
                        note->FMPhase--;
                    }
                }else{
                    note->phase += note->frequency/ kGraphSampleRate; //Adjust phase without deviation
                }
                // Calculate the next sample
                signal += instrument->wave_function(note->phase,instrument->wave_parameter)*track->note_amplitude[x];
                // Reset the phase value to prevent the float from overflowing
                if (note->phase >= 1){
                    note->phase--;
                }
            } //Else nothing added
        }
        if(signal > 1.0){
            signal = 1;
        }else if(signal < -1.0){
            signal = -1.0;
        }
        audioController->wave[audioController->wave_last] = signal;
        if (audioController->wave_last == 499) {
            audioController->wave_last = 0;
        }else{
            audioController->wave_last++;
        }
        outA[i] = (SInt16)(signal * 32767.0f);
    }
    return noErr;
}

非常感谢您的回答。


1
这可能是一个适合于http://dsp.stackexchange.com(或者也许是http://avp.stackexchange.com)的好问题。 - mtrw
3个回答

8

Redeye:

回答你的主要问题,除正弦波外调制其他波形是完全可以的。事实上,这正是FM合成最擅长的。调制正弦波会产生非常无聊的声音输出,但当你使用相同调制的更复杂波形时,你会得到更有趣的结果。

这最多是一种过度简化,可能完全不正确。使用正弦波调制正弦波完全能够创造出各种复杂且不“无聊”的声音。

相比之下,使用复杂波形会大量增加产生的旁系频带数量,并使得预测结果变得更加困难。大多数关于FM合成的文档 - 实际上在许多常见情况下包括雅马哈的“FM” - 都只涉及正弦波。

顺便说一下(如果你还不知道),最著名的FM合成器可能是雅马哈DX7,在其所处的年代具有革命性(也是最早具有MIDI的合成器之一)。

另一个需要提到的是,FM合成是数字时代的开端,因此波形是以数字方式产生的,因此使用了比正弦/方/三角波更复杂的波形来创建有趣的声音。”

这完全是错误的,毫无疑问。DX7和雅马哈早期的许多FM - 实际上是PM - 合成器只提供正弦波,但它们仍然能够产生许多非“无聊”的声音,就像我上面所说的那样。没有涉及到任何“更复杂的波形”。

只有后来雅马哈才添加了其他波形,与正弦波产生的旁系频带的可预测性相比,它们的实用性有些值得怀疑,正如我上面所述。

这可能是你需要做的以获得更好的声音 - 而不只是生成一个正弦波来调制,使用复杂波形。

或者只需使用好的参数组合(比例、指数等)的正弦波。

FM / PM使用正弦波并不会立即为许多用户产生工作室级别的结果(或者可能只是模拟类似模拟合成器的结果),但这并不意味着它无法做到。


5
最终我决定使用相位调制。我发现许多合成器即使被标记为FM,也会使用相位调制。
实现起来很简单:
signal += wave_function(note_phase * note_frequency / sample_rate + fm_index * sin(note_phase * fm_frequency * pi / sample_rate))*note_amplitude

1
你能否提供一些你认为最有帮助的资源?我在使用两个算子的FM合成时取得了不错的效果,但是当我尝试串联三个算子时,结果变得很奇怪。你有没有尝试过这种方式的FM合成?我已经使用了你提到的公式及其FM版本,但结果完全相同! - Phil Freihofner

3
很好的问题,我会尽力提供一些想法和建议...
回答你的主要问题,除了正弦波之外调制其他波形是完全可以的。实际上,这正是FM擅长的。调制正弦波会产生非常无聊的声音输出,但是当你输入具有相同调制的更复杂波形时,就会得到更有趣的结果。值得一提的是(如果你还不知道),最著名的 FM 合成器可能是Yamaha DX7,它在当时是革命性的(也是最早具有 MIDI 的合成器之一)。
另一个需要提到的事情是,FM 合成是数字时代的开始,因此波形是通过数字方式生成的,因此使用比正弦/方/三角波更复杂的波形来创建有趣的声音。这可能是你需要做的以获得更好的声音——而不仅仅是生成正弦波进行调制,使用复杂波形。
通过查看您的代码,看起来您正在正确进行 FM。然而,我认为调制频率通常是固定的,而不像您的代码中与音符频率的比例一样。也许值得尝试这种方法,看看是否听起来更符合您的要求。
希望这能有所帮助。

感谢您的回答。最终我决定采用相位调制。 - Matthew Mitchell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接