java - 更改波表音高(重新采样)

标签 java math audio

我一直在使用数学方程式为 android 做一些基本的综合。公式保持并变得越来越复杂的问题。所以我开始使用波表合成。
到目前为止,我可以根据字节数组和预定频率播放单个音符。然而,当寻找公式来重新采样样本并改变波表的音高时......我只是找不到一个简单的解决方案。有整个图书馆可以做到这一点。但感觉应该是一个简单的公式,通过下采样或过采样来改变采样频率。
有没有一种简单的方法可以做到这一点?
例如:输入是 [0,0.5,1,0.5,0...]让我们假装它是C4
要使其成为 C3,预期输出应为 [0,0.25,0.5,0.75,1,0.75,0.5,0.25,0...]基本上它是和android的audiopool“速率”属性,但只是重新采样字节的公式,我在任何地方都没有找到
编辑:
感谢 Phil Freihofner 指导我找到答案。然而,他提供的原始公式并没有在过度采样的情况下平滑结束。但这是一个非常好的开始,我只需要顺利结束。我将附上我的结束代码,以防有人想要一个简单的函数来“拉伸(stretch)”一个浮点数组。

public static void main(String[] args) {
        float[] sample = {0f, 0.5f, 1f, 0.5f, 0f, 0.5f, 1f, 0.5f, 0};

        System.out.println(Arrays.toString(resample(sample, 2f)));
        //[0.0, 0.25, 0.5, 0.75, 1.0, 0.75, 0.5, 0.25, 0.0, 0.25, 0.5, 0.75, 1.0, 0.75, 0.5, 0.25, 0.125, 0.0]
        System.out.println(Arrays.toString(resample(sample, 3f)));
        //[0.0, 0.16666667, 0.33333334, 0.5, 0.6666667, 0.8333333, 1.0, 0.8333334, 0.6666666, 0.5, 0.33333337, 0.16666663, 0.0, 0.16666675, 0.33333325, 0.5, 0.66666675, 0.83333325, 1.0, 0.83333325, 0.66666675, 0.5, 0.33333325, 0.16666675, 0.111111164, 0.055555582, 0.0]
        System.out.println(Arrays.toString(resample(sample, 0.5f)));
        //[0.0, 1.0, 0.0, 1.0, 0.0]
        System.out.println(Arrays.toString(resample(sample, 1f)));
        //[0.0, 0.5, 1.0, 0.5, 0.0, 0.5, 1.0, 0.5, 0.0]
        System.out.println(Arrays.toString(resample(sample, 4.3f)));
        //[0.0, 0.116279066, 0.23255813, 0.3488372, 0.46511626, 0.5813953, 0.6976744, 0.81395346, 0.9302325, 0.95348847, 0.83720934, 0.72093034, 0.6046512, 0.4883722, 0.37209308, 0.25581408, 0.13953495, 0.023255944, 0.09302306, 0.20930219, 0.3255813, 0.44186044, 0.5581393, 0.67441845, 0.7906976, 0.9069767, 0.9767444, 0.8604653, 0.74418616, 0.62790704, 0.51162815, 0.39534903, 0.2790699, 0.16279078, 0.04651189, 0.034883916, 0.023255944, 0.011627972, 0.0]
    }

    public static float[] resample(float[] sample, float rate) {
        if (rate == 1) {
            return sample;
        }
        int newLength = Math.round(sample.length * rate); 
        int rateUnit = (int) Math.round(rate);
        float[] result = new float[newLength];
        for (int i = 0; i < newLength - rateUnit; i++) {
            float val = (i / rate);
            int o = (int) val;
            result[i] = sample[o + 1] * (val - o)
                    + sample[o] * ((o + 1) - val);
        }
        int startIndex = newLength - rateUnit;

        for (int u = 0; u < newLength - startIndex; u++) {
            result[startIndex + u] = result[startIndex - 1] + (u + 1) * 
                    (sample[sample.length - 1] - result[startIndex - 1]) / rateUnit;
        }
        return result;
    }

最佳答案

假设您有一个 float 波表。
然后你可以做的是创建一个“光标”来遍历表。此游标不必是整数,它可以是浮点数。当您的光标落在波表的两个元素之间的值上时,您可以使用线性插值来计算“足够好”的值以返回。
考虑第一个波表。当您使用增量为 1 的游标时,它会播放 C4。如果您将增量设为 0.5 并使用线性插值,那么您将返回的值将与您的表中的内容相同,但要低一个 Octave 。
但是您可以选择任何增量值,甚至可以随时间更改它(以获得滑音)。
光标增量的确切大小将取决于波表的大小和采样率以及所需的音高。
我正在为我编码的 FM 合成器使用波表。正弦波表有 1024 个增量,用于单个波、浮点分辨率。它似乎运作良好。声音非常清晰——与我的旧 Yamaha DX7 或第二代 DX7S 相比,量化噪音肯定要少。
这是我目前使用线性插值返回给定光标值的波表值的代码:

public static float get(float i)
{
    final int idx = (int)i;
    
    return data[idx + 1] * (i - idx) 
            + data[idx] * ((idx + 1) - i);
}
数据表为 float[]长度为 1025,而周期为 1024 步。我有 data[0] == data[1024]以便上述算法适用于 0 <= cursor < 1024 范围内的任何游标值.当光标等于或超过 1024 时,我减去 1024。这样,可以无限增加光标,创建任何持续时间的音调。

关于java - 更改波表音高(重新采样),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64417603/

相关文章:

java - 无法从完全托管的 Google Cloud Run 发出出站请求

audio - 如何在XAudio2中设置子混音的输出? ( metro )

java - StreamingOutput 不抛出 WebApplicationException,返回空响应

java - 在Windows命令行上运行jar文件中子包中的类

algorithm - 找出液体到达第 n 个杯子所需的时间

python - 如何使用 numpy 创建一个从数据数组返回二维值的函数?

javascript - 为什么 Math.floor() 优于 Math.round()? [JavaScript]

android - 如何在固定的时间内产生特定频率的音调

iphone - 根据时间搜索音频文件

java - 如果语句和 && 给出 IDE 错误 'Syntax error on token "=", <= expected'