使用AKSampleDescriptor

时间:2018-10-27 15:39:45

标签: ios macos audiokit

使用AKSamplerDescriptor

我使用的是经过改编的AKSampler示例,其中尝试使用Fluid.sf3 melodicSounds的sforzando输出。 Sforzando会为每种乐器创建.sfz文件,但所有文件都指向全局样本指向一个巨大的.wav文件。

在所有instrument.sfz文件中,都有要使用的wave文件部分的偏移量和端点说明。

当我加载.sfz文件时,由于内存问题而崩溃。似乎对于.sfz文件中的每个定义的区域,都将再次加载完整的.wav文件(140 mB)。

最有可能的是,如在AKSampler示例中所做的那样,使用AKSampleDescriptor加载示例文件将在重新加载完整的.wav文件时忽略偏移量和端点(AKSampleDescriptor.startPoint和AKSampleDescriptor.endPoint)。

有没有一种方法可以只从样本文件中加载所需的从头到尾的零件,因为完整的文件中包含所有乐器的样本数据(我知道并使用了复音,一次只能提取一部乐器并且工作正常,但这是供其他用途)

或者,对我而言,这似乎是最好的,只需加载一次文件,然后将sampledescriptor指向内存中的数据

1 个答案:

答案 0 :(得分:0)

好的建议,罗布。我只是自己遇到过这个巨大的WAV问题,之前从未见过。我也在使用Sforzando进行转换。我将研究为AKSampler添加必要的功能。同时,编写一个程序将一个WAV文件切成较小的块并相应地调整SFZ可能会更容易。

这里有一些Python 2.7代码可以做到这一点,我已将其成功地用于Sforzando转换的sf2音型。它可能需要进行更改才能为您工作-sfz文件之间存在巨大差异-但至少它可以帮助您入门。这段代码需要PyDub库来处理WAV音频。

import os
import re
from pydub import AudioSegment

def stripComments(text):
    def replacer(match):
        s = match.group(0)
        if s.startswith('/'):
            return " " # note: a space and not an empty string
        else:
            return s
    pattern = re.compile(
        r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
        re.DOTALL | re.MULTILINE
    )
    return re.sub(pattern, replacer, text)

def updateSplitList(splitList, regionLabels, values):
    if len(values) > 3:
        start = int(values['offset'])
        length = int(values['end']) - start
        name = regionLabels.pop(0)
        splitList.add((name, start, length))

def lookupSplitName(splitList, offset, end):
    for (name, start, end) in splitList:
        if offset == start and end == end:
            return name
    return None

def outputGroupAndRegion(outputFile, splitList, values):
    if values.has_key('lokey') and values.has_key('hikey') and values.has_key('pitch_keycenter'):
        outputFile.write('<group> lokey=%s hikey=%s pitch_keycenter=%s\n' % (values['lokey'], values['hikey'], values['pitch_keycenter']))
    elif values.has_key('key') and values.has_key('pitch_keycenter'):
        outputFile.write('<group> key=%s pitch_keycenter=%s\n' % (values['key'], values['pitch_keycenter']))
    if len(values) > 3:
        outputFile.write('    <region> ')
        if values.has_key('lovel') and values.has_key('hivel'):
            outputFile.write('lovel=%s hivel=%s ' % (values['lovel'], values['hivel']))
        if values.has_key('tune'):
            outputFile.write('tune=%s ' % values['tune'])
        if values.has_key('volume'):
            outputFile.write('volume=%s ' % values['volume'])
        if values.has_key('offset'):
            outputFile.write('offset=0 ')
        if values.has_key('end'):
            outputFile.write('end=%d ' % (int(values['end']) - int(values['offset'])))
        if values.has_key('loop_mode'):
            outputFile.write('loop_mode=%s ' % values['loop_mode'])
        if values.has_key('loop_start'):
            outputFile.write('loop_start=%d ' % (int(values['loop_start']) - int(values['offset'])))
        if values.has_key('loop_end'):
            outputFile.write('loop_end=%d ' % (int(values['loop_end']) - int(values['offset'])))
        outputFile.write('sample=samples/%s' % lookupSplitName(splitList, int(values['offset']), int(values['end'])) + '.wav\n')

def process(inputFile, outputFile):

    # create a list of region labels
    regionLabels = list()
    for line in open(inputFile):
        if line.strip().startswith('region_label'):
            regionLabels.append(line.strip().split('=')[1])

    # read entire input SFZ file
    sfz = open(inputFile).read()

    # strip comments and create a mixed list of <header> tags and key=value pairs
    sfz_list = stripComments(sfz).split()

    inSection = "none"
    default_path = ""
    global_sample = None
    values = dict()
    splitList = set()

    # parse the input SFZ data and build up splitList
    for item in sfz_list:
        if item.startswith('<'):
            inSection = item
            updateSplitList(splitList, regionLabels, values)
            values.clear()
            continue
        elif item.find('=') < 0:
            #print 'unknown:', item
            continue

        key, value = item.split('=')
        if inSection == '<control>' and key == 'default_path':
            default_path = value.replace('\\', '/')
        elif inSection == '<global>' and key == 'sample':
            global_sample = value.replace('\\', '/')
        elif inSection == '<region>':
            values[key] = value

    # split the wav file
    bigWav = AudioSegment.from_wav(global_sample)
    #print "%d channels, %d bytes/sample, %d frames/sec" % (bigWav.channels, bigWav.sample_width, bigWav.frame_rate)
    frate = float(bigWav.frame_rate)
    for (name, start, length) in splitList:
        startMs = 1000 * start / frate
        endMs = 1000 * (start + length) / frate
        wav = bigWav[startMs : endMs]
        wavName = 'samples/' + name + '.wav'
        wav.export(wavName, format='wav')

    # parse the input SFZ data again and generate the output SFZ
    for item in sfz_list:
        if item.startswith('<'):
            inSection = item
            outputGroupAndRegion(outputFile, splitList, values)
            values.clear()
            continue
        elif item.find('=') < 0:
            #print 'unknown:', item
            continue

        key, value = item.split('=')
        if inSection == '<control>' and key == 'default_path':
            default_path = value.replace('\\', '/')
        elif inSection == '<global>' and key == 'sample':
            global_sample = value.replace('\\', '/')
        elif inSection == '<region>':
            values[key] = value


dirPath = '000'
fileNameList = os.listdir(dirPath)
for fileName in fileNameList:
    if fileName.endswith('.sfz'):
        inputFile = os.path.join(dirPath, fileName)
        outputFile = open(fileName, 'w')
        print fileName
        process(inputFile, outputFile)