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

huangapple go评论64阅读模式
英文:

Change wavetable pitch (Resample)

问题

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;
}
英文:

I've been using math equations to do some basic synthesis for android. The problem with that the formulas keep and keep getting more complicated. So i'm starting to use wavetable synthesis.

So far i can play a single note based on an array of bytes and a predetermined frequency. However when looking for formulas to resample the samples and change the pitch of the wavetable... i just couldn't find a simple solution. There are whole libraries that do this. But it feels like it should be a simple formula to change the sample frequency by downsampling or oversampling.

Is there an easy way of doing that?

Ex: Input is [0,0.5,1,0.5,0...] let's pretend it's a C4

To make it a C3 the expected output should be [0,0.25,0.5,0.75,1,0.75,0.5,0.25,0...]

Basically it's and android's audiopool "rate" property but just the formula that resamples the bytes, which i haven't found anywhere

EDIT:

Thank you to Phil Freihofner for guiding me to the answer. However the original formula he provided didn't smoothed out the end in case of oversampling. But it was a really nice start and i only had to smooth the end. I will attach my end code in case anyone wants a simple function to "Stretch out" a float array.

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 &lt; 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 &lt; newLength - startIndex; u++) {
result[startIndex + u] = result[startIndex - 1] + (u + 1) * 
(sample[sample.length - 1] - result[startIndex - 1]) / rateUnit;
}
return result;
}

答案1

得分: 1

让我们假设你有一个浮点数的波表。

然后你可以创建一个“游标”来遍历这个表。这个游标不一定要是整数,它可以是浮点数。当你的游标落在两个波表元素之间的值时,你可以使用线性插值来计算一个“足够好”的返回值。

考虑你所拥有的第一个波表。当你使用增量为1的游标时,它会播放C4音调。如果你将增量设为0.5并使用线性插值,你将返回与表中内容相同的值,只是音调降低了一个八度。

但你可以选择任何增量值,甚至随时间改变它(以获得滑音效果)。

游标的精确增量大小取决于波表的大小、采样率以及期望的音高。

我正在我的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);
}

data表是长度为1025的float[],而period为1024步。我设置了data[0] == data[1024],这样上述算法对于在范围0 <= cursor < 1024内的任何游标值都适用。当游标等于或超过1024时,我会减去1024。这样,可以无限地增加游标,创建任意持续时间的音调。

英文:

Let's say you have a wave table in floats.

What you can then do is create a "cursor" to iterate through the table. This cursor does NOT have to be an integer, it can be a float. When your cursor lands on a value that is in between two elements your wave table, you can use linear interpolation to calculate a "good enough" value to return.

Consider the first wave table the have. It plays C4 when you use a cursor with an increment of 1. If you make the increment 0.5 and use linear interpolation, the values that you would be returning would be the same as contents of your table that is an octave lower.

But you could chose any increment value, and even change it over time (to get glissandos).

The exact size of the increment for the cursor will depend on the size of the wave table and the sample rate as well as the desired pitch.

I am using wavetables for an FM synthesizer I coded. The sine wave table has 1024 increments for a single wave, float resolution. It seems to be working well. Sounds are quite clear--definitely less quantization noise than I get with my old Yamaha DX7 or the second generation DX7S.

Here is the code I'm currently using for returning the wave table value for a given cursor value, using linear interpolation:

public static float get(float i)
{
final int idx = (int)i;
return data[idx + 1] * (i - idx) 
+ data[idx] * ((idx + 1) - i);
}

The data table is a float[] of length 1025, while the period is 1024 steps. I have data[0] == data[1024] so that the above algorithm works for any cursor value in the range 0 &lt;= cursor &lt; 1024. When the cursor equals or exceeds 1024, I subtract 1024. This way, one can increment the cursor indefinitely, creating a tone of any duration.

huangapple
  • 本文由 发表于 2020年10月19日 03:43:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/64417603.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定