You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在.NET C#中如何实现WAV音高转调及单周期和弦生成?

让我来一步步帮你解决这两个音频处理的问题,都是C#里很实用的音频开发场景:

1. 在.NET C#中对.wav文件进行音高转调

音高转调的核心是改变音频的频率——频率越高,音高越高。根据对时长的要求,我们有两种实现方式,这里用.NET生态中最常用的音频库NAudio来完成:

简单转调(改变播放速度,时长同步变化)

这种方式适合对时长要求不高的场景,原理是通过修改采样率改变播放速度,间接实现音高变化:

using NAudio.Wave;

public void ChangePitchBySpeed(string inputPath, string outputPath, float pitchShiftFactor)
{
    // pitchShiftFactor >1 升高音高,<1 降低音高,比如1.5对应纯五度升高
    using var reader = new WaveFileReader(inputPath);
    // 按音高因子调整采样率
    var newSampleRate = (int)(reader.WaveFormat.SampleRate * pitchShiftFactor);
    var convertedStream = new WaveFormatConversionStream(
        new WaveFormat(newSampleRate, reader.WaveFormat.BitsPerSample, reader.WaveFormat.Channels),
        reader);
    WaveFileWriter.CreateWaveFile(outputPath, convertedStream);
}

比如把采样率提高1.5倍,播放时音高会升高纯五度,但时长会缩短为原来的2/3。

变速不变调转调(保持原时长)

如果需要音高变化但时长不变,就得用**WSOLA(波形相似叠加)**这类专业算法,NAudio内置了相关实现:

using NAudio.Wave;
using NAudio.Wave.SampleProviders;

public void PitchShiftWithoutChangingDuration(string inputPath, string outputPath, int semitones)
{
    // semitones 为正升高,为负降低,比如7对应纯五度升高
    using var reader = new WaveFileReader(inputPath);
    var sampleProvider = reader.ToSampleProvider();
    
    // 计算音高转换因子:2^(半音数/12)
    float pitchFactor = (float)Math.Pow(2, semitones / 12.0);
    // WSOLA算法实现变速不变调:音高因子和 tempo 因子乘积为1,保证时长不变
    var wsolaProvider = new WsolaSampleProvider(sampleProvider)
    {
        PitchFactor = pitchFactor,
        TempoFactor = 1 / pitchFactor
    };
    
    // 转换回WaveStream并保存
    using var waveProvider = wsolaProvider.ToWaveProvider();
    WaveFileWriter.CreateWaveFile(outputPath, waveProvider);
}
2. 生成可完美循环的单周期和弦

你说的纯五度案例非常典型,核心是让不同音高的单周期波形循环后的总长度完全一致,这样混合后首尾衔接没有断层,实现完美循环。以下是具体实现步骤:

核心原理

纯五度的频率比是3:2:原始单周期波形的周期为T,转调7个半音后的波形周期为T*(2/3)。原始波形循环2次的总长度是2T,转调后的波形循环3次的总长度是3*(2/3)T=2T,两者长度完全匹配,混合后自然能完美循环。

实现代码(C# + NAudio)

步骤1:加载单周期波形

先读取仅包含一个周期的.wav文件:

using NAudio.Wave;

public float[] LoadSingleCycleWave(string wavePath)
{
    using var reader = new WaveFileReader(wavePath);
    var sampleProvider = reader.ToSampleProvider();
    var samples = new float[(int)reader.Length / (reader.WaveFormat.BitsPerSample / 8)];
    sampleProvider.Read(samples, 0, samples.Length);
    return samples;
}

步骤2:生成转调后的单周期波形

通过线性插值调整原始波形的样本数,得到对应音高的单周期波形:

public float[] PitchShiftSingleCycle(float[] originalSamples, int semitones)
{
    float pitchFactor = (float)Math.Pow(2, semitones / 12.0);
    // 新单周期的样本数 = 原始样本数 / 音高因子
    int newSampleCount = (int)Math.Round(originalSamples.Length / pitchFactor);
    
    var shiftedSamples = new float[newSampleCount];
    for (int i = 0; i < newSampleCount; i++)
    {
        float originalIndex = i * pitchFactor;
        int leftIndex = (int)Math.Floor(originalIndex);
        // 循环取索引,保证单周期的连续性
        int rightIndex = (leftIndex + 1) % originalSamples.Length;
        float fraction = originalIndex - leftIndex;
        // 线性插值生成新样本
        shiftedSamples[i] = originalSamples[leftIndex] * (1 - fraction) + originalSamples[rightIndex] * fraction;
    }
    return shiftedSamples;
}

步骤3:循环波形并混合

把原始波形循环2次、转调后的波形循环3次,再混合样本并归一化防止爆音:

public float[] GeneratePerfectLoopChord(float[] originalCycle, float[] shiftedCycle)
{
    int totalLength = originalCycle.Length * 2;
    // 验证转调后的波形循环3次长度是否匹配
    if (shiftedCycle.Length * 3 != totalLength)
    {
        throw new InvalidOperationException("转调后的波形参数不匹配,无法生成完美循环");
    }
    
    var mixedSamples = new float[totalLength];
    
    // 填充原始循环波形
    for (int i = 0; i < totalLength; i++)
    {
        mixedSamples[i] += originalCycle[i % originalCycle.Length];
    }
    
    // 填充转调后的循环波形
    for (int i = 0; i < totalLength; i++)
    {
        mixedSamples[i] += shiftedCycle[i % shiftedCycle.Length];
    }
    
    // 归一化音量
    float maxAmplitude = mixedSamples.Max(Math.Abs);
    if (maxAmplitude > 0)
    {
        for (int i = 0; i < totalLength; i++)
        {
            mixedSamples[i] /= maxAmplitude;
        }
    }
    
    return mixedSamples;
}

步骤4:保存为可循环的.wav文件

最后把混合后的样本写入文件:

public void SaveWaveFile(float[] samples, string outputPath, WaveFormat waveFormat)
{
    using var writer = new WaveFileWriter(outputPath, waveFormat);
    foreach (float sample in samples)
    {
        writer.WriteSample(sample);
    }
}

调用示例

public void RunChordGenerator()
{
    string singleCyclePath = "single_cycle.wav";
    string outputPath = "perfect_loop_power_chord.wav";
    
    // 加载原始单周期波形
    float[] originalCycle = LoadSingleCycleWave(singleCyclePath);
    using var reader = new WaveFileReader(singleCyclePath);
    var waveFormat = reader.WaveFormat;
    
    // 转调7个半音(纯五度)
    float[] shiftedCycle = PitchShiftSingleCycle(originalCycle, 7);
    
    // 生成混合后的完美循环和弦
    float[] mixedSamples = GeneratePerfectLoopChord(originalCycle, shiftedCycle);
    
    // 保存文件
    SaveWaveFile(mixedSamples, outputPath, waveFormat);
}

额外提示

  • 确保原始单周期波形首尾平滑衔接,否则循环播放会有爆音
  • 对于其他和弦(比如大三和弦),可以通过频率比计算最小公倍数来确定每个波形的循环次数。比如大三和弦频率比是4:5:6,原始波形循环6次、3音循环5次、5音循环4次,总长度一致就能完美循环。

内容的提问来源于stack exchange,提问作者Stefan Bookholt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:03:59