问题
I had to upgrade from AudioKit 4.0 to AudioKit 4.2 due to an incompatibility of AudioKit 4.0 with latest swift language and Xcode.
However, now my project cannot be compiled because loadMelodicSoundFont is not a member of AKSampler anymore while I'm using this method to load sf2 sound file.
I could find a documentation for 4.1 only on http://audiokit.io/docs/ and 4.1 has loadMelodicSoundFont apparently. And no documentation for 4.2 I could find.
So what is the replacement for this method in AudioKit 4.2?
回答1:
The new AKSampler
class in the latest versions of AudioKit is an entirely new custom sampler. However, as of right now it no longer handles SoundFont files natively (that's what your sf2 file is).
The easiest way for you would simply be to switch to AKAppleSampler
, which is the fully featured sampler from previous versions of AudioKit. It relies on the Apple AU code and is still able to load SoundFont files.
So in practice you would simply rename your references to AKSampler
to AKAppleSampler
in your code.
回答2:
You can rewrite the loadBetterSFZ sampler-extension function in the AKsampler example. This only reads for the included converted files and presumes the sample file is the last part of a region.
I did so and now I can use to sfz converted sf2 files from polyphony(windows) and sforzando and read the file for as far as I understand AKSampler can hold. I made a class to read the sfz file into a data structure that holds the data for reuse. For the sampler I wrote an extension that loads from the created data. Its all quick and dirty (I am not a programmer!). There are many different ways sfz files seem to be structured and I only tried a dozen or so.
example: add class SFZData to conductor in the example
get data with func SFZData.parseSFZ(folderPath: String, sfzFileName: String)->SFZ?
use SFZ for the extension in sampler: func loadSFZData(sfz:SFZ)
//here is what I did(all last versions and works on iPhone 6s)
import Foundation
class regionData {
var lovel: Int32 = -1 //not set, use group
var hivel: Int32 = -1
var lokey: Int32 = -1
var hikey: Int32 = -1
var pitch: Int32 = -1
var tune: Int32 = 0
var transpose: Int32 = 0
var loopmode: String = ""
var loopstart: Float32 = 0
var loopend: Float32 = 0
var startPoint: Float32 = 0
var endPoint: Float32 = 0
var sample: String = ""
init(){
}
}
class groupData {
var lovel: Int32 = 0
var hivel: Int32 = 127
var lokey: Int32 = 0
var hikey: Int32 = 127
var pitch: Int32 = 60
var loopmode: String = ""
var sample: String = ""
var regions = [regionData]()
init(){
}
}
class globalData {
var samplePath = ""
var lovel: Int32 = 0
var hivel: Int32 = 127
var sample: String = ""
var groups = [groupData]()
//ampdata?
//filterdata?
init(){
}
}
class ampData {
// in global and or group?
}
class SFZ {
var sfzName = ""
var baseURL : URL!
var global = globalData()
var group = [groupData?]()
init(){
//
}
}
class SFZdata {
var sfzChuncks = [String:SFZ]()
init(){
}
func getData(folderPath: String, sfzFileName: String)->SFZ?{
let sfzdata = sfzChuncks[sfzFileName]
if sfzdata != nil {
return sfzdata
}
return parseSFZ(folderPath:folderPath,sfzFileName:sfzFileName)
}
func parseSFZ(folderPath: String, sfzFileName: String)->SFZ? {
//let globalName = "<global>"
//let groupName = "<group>"
let regionName = "<region>"
var filePosition : String.Index
var chunck = ""
var data: String
let sfz = SFZ()
//stopAllVoices()
//unloadAllSamples()
let baseURL = URL(fileURLWithPath: folderPath)
let sfzURL = baseURL.appendingPathComponent(sfzFileName)
do {
data = try String(contentsOf: sfzURL, encoding: .ascii)
}catch {
debugPrint("file not found")
return nil
}
sfz.sfzName = sfzFileName
filePosition = data.startIndex
while filePosition != data.endIndex {
chunck = findHeader(data: data,dataPointer: &filePosition)
switch chunck {
case "<global>":
//get end of gobal and read data
let globaldata = readChunck(data: data, dataPointer: &filePosition)
let trimmed = String(globaldata.trimmingCharacters(in: .whitespacesAndNewlines))
sfz.global = readGlobal(globalChunck: trimmed)!
break
case "<group>":
//get end of group and read data
//first read this one the
let groupdata = readChunck(data: data, dataPointer: &filePosition)
let trimmed = String(groupdata.trimmingCharacters(in: .whitespacesAndNewlines))
let mygroup = readGroup(groupChunck: trimmed)
chunck = findHeader(data: data, dataPointer: &filePosition)
while chunck == regionName {
//read region and append
let regiondata = readChunck(data: data, dataPointer: &filePosition)
let trimmed = String(regiondata.trimmingCharacters(in: .whitespacesAndNewlines))
let myRegion = readRegion(regionChunck: trimmed)
mygroup?.regions.append(myRegion)
chunck = findHeader(data: data, dataPointer: &filePosition)
}
if chunck != regionName && filePosition != data.endIndex {
//back to before header if ! endoffile
filePosition = data.index(filePosition, offsetBy: -(chunck.count))
}
sfz.group.append(mygroup)
break
// case region without group ? ignore
default:
//ignore
break
}
}
sfz.baseURL = URL(fileURLWithPath: folderPath)
sfzChuncks.updateValue(sfz, forKey: sfzFileName)
return sfz
}
func findHeader(data:String, dataPointer:inout String.Index)->(String) {
if dataPointer == data.endIndex {
return ("")
}
while dataPointer != data.endIndex {
if data[dataPointer] == "<" { break }
dataPointer = data.index(after: dataPointer)
}
if dataPointer == data.endIndex {
return ("")
}
let start = dataPointer
while dataPointer != data.endIndex {
if data[dataPointer] == ">" { break }
dataPointer = data.index(after: dataPointer)
}
dataPointer = data.index(after: dataPointer)
if dataPointer == data.endIndex {
return ("")
}
return (String(data[start..<dataPointer]))
}
func readChunck(data:String,dataPointer:inout String.Index)->String{
var readData = ""
if dataPointer == data.endIndex { return readData }
while dataPointer != data.endIndex {
if data[dataPointer] == "<" {
break
} else {
readData.append(data[dataPointer])
dataPointer = data.index(after: dataPointer)
}
}
if dataPointer == data.endIndex {return readData }
if data[dataPointer] == "<" {
dataPointer = data.index(before: dataPointer)
}
return readData
}
func readGlobal(globalChunck:String)->globalData?{
let globaldata = globalData()
var samplestring = ""
var global = globalChunck
for part in globalChunck.components(separatedBy: .newlines){
if part.hasPrefix("sample") {
samplestring = part
}
}
if samplestring == "" {
//check for structure
if global.contains("sample") {
//get it out
var pointer = global.startIndex
var offset = global.index(pointer, offsetBy: 6, limitedBy: global.endIndex)
var s = ""
while offset != global.endIndex {
s = String(global[pointer..<offset!])
if s.contains("sample") {break}
pointer = global.index(after: pointer)
offset = global.index(pointer, offsetBy: 6, limitedBy: global.endIndex)
}
if s.contains("sample") {
//read to end
samplestring = String(global[pointer..<global.endIndex])
}
}
}
if samplestring != "" {
globaldata.sample = samplestring.components(separatedBy: "sample=")[1].replacingOccurrences(of: "\\", with: "/")
global = global.replacingOccurrences(of: samplestring, with: "", options: NSString.CompareOptions.literal, range: nil)
}
for part in global.components(separatedBy: .newlines) {
if part.hasPrefix("lovel") {
globaldata.lovel = Int32(part.components(separatedBy: "=")[1])!
} else if part.hasPrefix("hivel") {
globaldata.hivel = Int32(part.components(separatedBy: "=")[1])!
} else if part.hasPrefix("sample") {
globaldata.sample = part.components(separatedBy: "sample=")[1].replacingOccurrences(of: "\\", with: "/")
}
}
return globaldata
}
func readGroup(groupChunck:String)->groupData?{
let groupdata = groupData()
var samplestring = ""
var group = groupChunck
for part in groupChunck.components(separatedBy: .newlines){
if part.hasPrefix("sample") {
samplestring = part
}
}
if samplestring == "" {
//check for structure
if group.contains("sample") {
//get it out
var pointer = group.startIndex
var offset = group.index(pointer, offsetBy: 6, limitedBy: group.endIndex)
var s = ""
while offset != group.endIndex {
s = String(group[pointer..<offset!])
if s.contains("sample") {break}
pointer = group.index(after: pointer)
offset = group.index(pointer, offsetBy: 6, limitedBy: group.endIndex)
}
if s.contains("sample") {
//read to end
samplestring = String(group[pointer..<group.endIndex])
}
}
}
if samplestring != "" {
groupdata.sample = samplestring.components(separatedBy: "sample=")[1].replacingOccurrences(of: "\\", with: "/")
group = group.replacingOccurrences(of: samplestring, with: "", options: NSString.CompareOptions.literal, range: nil)
}
for part in group.components(separatedBy: .whitespacesAndNewlines) {
if part.hasPrefix("lovel") {
groupdata.lovel = Int32(part.components(separatedBy: "=")[1])!
} else if part.hasPrefix("hivel") {
groupdata.hivel = Int32(part.components(separatedBy: "=")[1])!
} else if part.hasPrefix("lokey") {
groupdata.lokey = Int32(part.components(separatedBy: "=")[1])!
} else if part.hasPrefix("hikey") {
groupdata.hikey = Int32(part.components(separatedBy: "=")[1])!
} else if part.hasPrefix("pitch_keycenter") {
groupdata.pitch = Int32(part.components(separatedBy: "=")[1])!
}else if part.hasPrefix("loop_mode") {
groupdata.loopmode = part.components(separatedBy: "=")[1]
}
}
return groupdata
}
func readRegion(regionChunck:String)->regionData{
let regiondata = regionData()
var samplestring = ""
var region = regionChunck
for part in regionChunck.components(separatedBy: .newlines){
if part.hasPrefix("sample") {
samplestring = part
}
}
// this for formats in wich ther are no newlines between region elements
if samplestring == "" {
//check for structure
if region.contains("sample") {
//get it out
var pointer = region.startIndex
var offset = region.index(pointer, offsetBy: 6, limitedBy: region.endIndex)
var s = ""
while offset != region.endIndex {
s = String(region[pointer..<offset!])
if s.contains("sample") {break}
pointer = region.index(after: pointer)
offset = region.index(pointer, offsetBy: 6, limitedBy: region.endIndex)
}
if s.contains("sample") {
//read to end
samplestring = String(region[pointer..<region.endIndex])
}
}
}
if samplestring != "" {
regiondata.sample = samplestring.components(separatedBy: "sample=")[1].replacingOccurrences(of: "\\", with: "/")
region = region.replacingOccurrences(of: samplestring, with: "", options: NSString.CompareOptions.literal, range: nil)
}
for part in region.components(separatedBy: .whitespacesAndNewlines) {
if part.hasPrefix("lovel") {
regiondata.lovel = Int32(part.components(separatedBy: "=")[1])!
} else if part.hasPrefix("hivel") {
regiondata.hivel = Int32(part.components(separatedBy: "=")[1])!
} else if part.hasPrefix("key=") {
regiondata.pitch = Int32(part.components(separatedBy: "=")[1])!
regiondata.lokey = regiondata.pitch
regiondata.hikey = regiondata.pitch
}else if part.hasPrefix("transpose") {
regiondata.transpose = Int32(part.components(separatedBy: "=")[1])!
} else if part.hasPrefix("tune") {
regiondata.tune = Int32(part.components(separatedBy: "=")[1])!
} else if part.hasPrefix("lokey") { // sometimes on one line
regiondata.lokey = Int32(part.components(separatedBy: "=")[1])!
} else if part.hasPrefix("hikey") {
regiondata.hikey = Int32(part.components(separatedBy: "=")[1])!
} else if part.hasPrefix("pitch_keycenter") {
regiondata.pitch = Int32(part.components(separatedBy: "=")[1])!
} else if part.hasPrefix("loop_mode") {
regiondata.loopmode = part.components(separatedBy: "=")[1]
} else if part.hasPrefix("loop_start") {
regiondata.loopstart = Float32(part.components(separatedBy: "=")[1])!
} else if part.hasPrefix("loop_end") {
regiondata.loopend = Float32(part.components(separatedBy: "=")[1])!
}else if part.hasPrefix("offset") {
regiondata.startPoint = Float32(part.components(separatedBy: "=")[1])!
}
else if part.hasPrefix("end") {
regiondata.endPoint = Float32(part.components(separatedBy: "=")[1])!
}
}
return regiondata
}
}
extension for sampler:
func loadSFZData(sfz:SFZ){
stopAllVoices()
unloadAllSamples()
for group in sfz.group {
for region in (group?.regions)! {
var sd = AKSampleDescriptor()
if region.pitch >= 0 {
sd.noteNumber = region.pitch
} else {
sd.noteNumber = (group?.pitch)!
}
var diff = Float(1)
if region.tune != 0 {
let fact = Float(pow(1.000578,Double(region.tune.magnitude)))
if region.tune < 0 {
diff *= fact
} else {
diff /= fact
}
}
sd.noteFrequency = Float(AKPolyphonicNode.tuningTable.frequency(forNoteNumber: MIDINoteNumber(sd.noteNumber-region.transpose)))*diff
if region.lovel >= 0 {
sd.minimumVelocity = region.lovel
} else {
sd.minimumVelocity = (group?.lovel)!
}
if region.hivel >= 0 {
sd.maximumVelocity = region.hivel
} else {
sd.maximumVelocity = (group?.hivel)!
}
if region.lokey >= 0 {
sd.minimumNoteNumber = region.lokey
} else {
sd.minimumNoteNumber = (group?.lokey)!
}
if region.hikey >= 0{
sd.maximumNoteNumber = region.hikey
} else {
sd.maximumNoteNumber = (group?.hikey)!
}
sd.loopStartPoint = region.loopstart
sd.loopEndPoint = region.loopend
var loopMode = ""
if region.loopmode != "" {
loopMode = region.loopmode
} else if group?.loopmode != "" {
loopMode = (group?.loopmode)!
}
sd.isLooping = loopMode != "" && loopMode != "no_loop"
sd.startPoint = region.startPoint
sd.endPoint = region.endPoint
// build sampldescriptor from region
// now sample
var sample = region.sample
if sample == "" { sample = (group?.sample)! }
if sample == "" { sample = sfz.global.sample}
if sample != "" {
let sampleFileURL = sfz.baseURL.appendingPathComponent(sample)
if sample.hasSuffix(".wv") {
loadCompressedSampleFile(from: AKSampleFileDescriptor(sampleDescriptor: sd, path: sampleFileURL.path))
} else {
if sample.hasSuffix(".aif") || sample.hasSuffix(".wav") {
let compressedFileURL = sfz.baseURL.appendingPathComponent(String(sample.dropLast(4) + ".wv"))
let fileMgr = FileManager.default
if fileMgr.fileExists(atPath: compressedFileURL.path) {
loadCompressedSampleFile(from: AKSampleFileDescriptor(sampleDescriptor: sd, path: compressedFileURL.path))
} else {
do {
let sampleFile = try AKAudioFile(forReading: sampleFileURL)
loadAKAudioFile(from: sd, file: sampleFile)
} catch {
debugPrint("error loading audiofile")
}
}
}
}
} //if sample
} //region
} //group
buildKeyMap()
restartVoices()
}
来源:https://stackoverflow.com/questions/50054135/what-is-the-replacement-for-aksampler-loadmelodicsoundfont-in-audiokit-4-2