批量设置WAV循环点(或任何文件块数据)

批量设置WAV循环点(或任何文件块数据)

波形文件格式支持名为“smpl”的数据块,大多数 DAW 和大多数采样器插件(如 Kontakt)都可以识别该数据块。数据块允许您为波形文件指定循环开始位置和循环结束位置。

http://www.piclist.com/techref/io/serial/midi/wave.html https://web.archive.org/web/20141226210234/http://www.sonicspot.com/guide/wavefiles.html#smpl

我如何在多个文件上批量获取/设置此数据块的属性?理想情况下,我想做这样的事情:

utility.exe input.wav output.wav --loopstart 44100 --loopend 88200

其中,它以 input.wav 作为输入,并输出 output.wav,其中开始和结束循环点分别插入 1 秒和 2 秒(假设音频为 44.1 kHz)。

我已经了解 SoX、libsndfile 等,但不幸的是它们没有此功能。还有其他方法可以实现吗?

答案1

由于情况非常特殊,我不确定这个解决方案是否最终会对任何人有所帮助,但无论如何我都会发布它以防万一。

我不相信互联网上存在可以设置波浪循环点的命令行实用程序,所以我最终在 Node.js 中编写了一个函数来实现这一点。

module.exports = function()
{
    this.SetLoop = function(inputPath, outputPath, start, end)
    {
        // load input wave
        var waveData = require('fs').readFileSync(inputPath);

        // get sample rate of wave file
        var sampleRateIndex;
        for(var i = 0; i < waveData.length-4; i++)
        {
            if (waveData.toString('utf8', i, i + 4) === 'fmt ') // look for fmt chunk - which contains sample rate among other things - per wave format specification
            {
                sampleRateIndex = i + 4 + 4 + 2 + 2; // sample rate is 12 bytes after start of 'fmt ' chunk id
                break;
            }
        }
        if (sampleRateIndex === undefined)
            return;
        var sampleRate = waveData.readUInt32LE(sampleRateIndex);

        // convert seconds to samples
        start = Math.floor(sampleRate*start);
        end = Math.floor(sampleRate*end);

        // find index (byte offset) of smpl chunk if it exists
        var smplIndex;
        for(var i = waveData.length-4-1; i >= 0; i--) // start search from end of file going backward, since the smpl chunk is typically written after the actual waveform data
        {
            if (waveData.toString('utf8', i, i + 4) === 'smpl')
            {
                smplIndex = i; // start of smpl chunk id
                break;
            }
        }

        // if the smpl chunk already exists, remove it
        if (smplIndex !== undefined)
        {
            var smplChunkSize = waveData.readUInt32LE(smplIndex + 4) + 8; // smpl chunk size is specified 4 bytes after start of smpl chunk id. add 8 bytes to include size of smpl chunk header itself
            waveData = Buffer.concat( [ waveData.slice(0, smplIndex) , waveData.slice(smplIndex + smplChunkSize) ] ); // splice smpl chunk from wave file data
        }

        // make new buffer to replace smpl chunk
        var smplChunk = Buffer.alloc(68); // the default smpl chunk written here is 60 bytes long. add 8 bytes to include size of smpl chunk header itself
        // all bytes other than the ones specified below default to 0 and represent default values for the smpl chunk properties
        smplChunk.write('smpl', 0, 4);
        smplChunk.writeUInt32LE(60, 4); // the default smpl chunk written here is 60 bytes long
        smplChunk.writeUInt32LE(60, 20); // middle C is MIDI note 60, therefore make MIDI unity note 60
        smplChunk.writeUInt32LE(1, 36); // write at byte offset 36 that there is one loop cue info in the file
        smplChunk.writeUInt32LE(start, 52); // write loop start point at byte offset 52
        smplChunk.writeUInt32LE(end, 56); // write loop end point at byte offset 56

        // append new smpl chunk to wave file
        waveData = Buffer.concat( [ waveData, smplChunk ] );

        // change wave file main header data to increase the file size to include smpl chunk (loop points)
        var fileSizeIndex;
        for(var i = 0; i < waveData.length-4; i++)
        {
            if (waveData.toString('utf8', i, i + 4) === 'RIFF') // look for RIFF chunk (should always be at the very beginning of file)
            {
                fileSizeIndex = i + 4; // file size is 4 bytes after start of RIFF chunk id
                break;
            }
        }
        if (fileSizeIndex === undefined)
            return;
        var fileSize = waveData.length-8; // get final length of wave file, minus 8 bytes to not include the RIFF chunk header itself
        waveData.writeUInt32LE(fileSize, fileSizeIndex); // write new file length

        // write new wave file
        require('fs').writeFileSync(outputPath, waveData);
    }
}

要使用此函数:

  1. 安装 Node.js
  2. 将上述函数保存在文本文件中并重命名为 tools.js
  3. 打开 Node.js 命令行窗口
  4. require('C:/path/to/where/you/saved/tools.js')();
  5. process.chdir('C:/path/to/wave/files');
  6. SetLoop('input.wav','output.wav',1,2) // set loop start at 1.0 sec and loop end at 2.0 sec

请注意,此代码完全替换了文件的 smpl 块,并因此丢弃了文件中任何现有的提示/循环点。

相关内容