AudioKit/DunneAudioKit

'loadAudioFile' is inaccessible due to 'internal' protection level

JanBurp opened this issue · 15 comments

sampler.loadAudioFile(from: description, file: file!)

Raises above error.
Should be a public func i suppose?

aure commented

I think this was done because you can't just load the file any old time safely. I could be wrong.

Thanks. If this is the case the example in the docs should change i suppose.

So what is the proper way to load audio files from SampleDescriptor ?

So what is the proper way to load audio files from SampleDescriptor ?

The way I originally wrote the code, you would call stopAllVoices(), then load samples as you wish, then call restartVoices(). I haven't kept up with how the code has changed, but this was the basic idea.

Have a look at the file Sampler+SFZ.swift to see a working example (or at least one that used to work when I wrote it).

My problem is that I re-wrote a sfz file parser since the one in Sampler+SFZ doesn't read the following one properly :
https://freepats.zenvoid.org/Piano/acoustic-grand-piano.html#UprightKW

@gregoireLem All I can suggest is that you use the Sampler+SFZ code as a guide for writing your own. I'm too tied up with other work to advise in greater detail. Perhaps @aure or one of the more active AudioKit devs can help?

This would work if loadAudioFile was public, but since it's internal, I don't know what I must do

extension Sampler {
    open func betterLoadUsingSfzFile(url: URL) {
        
        stopAllVoices()
        var lowNoteNumber: MIDINoteNumber = 0
        var highNoteNumber: MIDINoteNumber = 127
        var noteNumber: MIDINoteNumber = 60
        var lowVelocity: MIDIVelocity = 0
        var highVelocity: MIDIVelocity = 127
        var sample: String = ""
        var loopMode: String = ""
        var loopstart: Float32 = 0
        var loopend: Float32 = 0

        let samplesBaseURL = url.deletingLastPathComponent()
        print("Sample \(url)")
        do {
            let data = try String(contentsOf: url, encoding: .ascii)
            let lines = data.components(separatedBy: .newlines)
            for line in lines {
                let trimmed = String(line.trimmingCharacters(in: .whitespacesAndNewlines))
                if trimmed == "" || trimmed.hasPrefix("//") {
                    // ignore blank lines and comment lines
                    continue
                }
                for part in trimmed.components(separatedBy: .whitespaces) {
                    if part.hasPrefix("<global>") {
                        lowNoteNumber = 0
                        highNoteNumber = 127
                        noteNumber = 60
                        lowVelocity = 0
                        highVelocity = 127
                        sample = ""
                        loopstart = 0
                        loopend = 0
                    }
                    // group and region don't really tell us anything in the Kawai files
                    //if part.hasPrefix("<group>") {
                    //}
                    //else if part.hasPrefix("<region>") {
                    //}
                    else if part.hasPrefix("key=") {
                        noteNumber = UInt8(part.components(separatedBy: "=")[1])!
                        lowNoteNumber = noteNumber
                        highNoteNumber = noteNumber
                    } else if part.hasPrefix("lokey") {
                        lowNoteNumber = UInt8(part.components(separatedBy: "=")[1])!
                    } else if part.hasPrefix("hikey") {
                        highNoteNumber = UInt8(part.components(separatedBy: "=")[1])!
                    } else if part.hasPrefix("pitch_keycenter") {
                        noteNumber = UInt8(part.components(separatedBy: "=")[1])!
                    }
                    else if part.hasPrefix("lovel") {
                        lowVelocity = UInt8(part.components(separatedBy: "=")[1])!
                    } else if part.hasPrefix("hivel") {
                        highVelocity = UInt8(part.components(separatedBy: "=")[1])!
                    } else if part.hasPrefix("loop_mode") {
                        loopMode = part.components(separatedBy: "=")[1]
                    } else if part.hasPrefix("loop_start") {
                        loopstart = Float32(part.components(separatedBy: "=")[1])!
                    } else if part.hasPrefix("loop_end") {
                        loopend = Float32(part.components(separatedBy: "=")[1])!
                    } else if part.hasPrefix("sample") {
                        sample = trimmed.components(separatedBy: "sample=")[1].replacingOccurrences(of: "\\", with: "/")
                    }
                }

                if sample != "" {

                    let noteFrequency = Float(440.0 * pow(2.0, (Double(noteNumber) - 69.0) / 12.0))

                    let noteLog = "load \(noteNumber) \(noteFrequency) NN range \(lowNoteNumber)-\(highNoteNumber)"
                    Log("Sample \(noteLog) vel \(lowVelocity)-\(highVelocity) \(sample)")

                    let sampleDescriptor = SampleDescriptor(noteNumber: Int32(noteNumber),
                                                              noteFrequency: noteFrequency,
                                                              minimumNoteNumber: Int32(lowNoteNumber),
                                                              maximumNoteNumber: Int32(highNoteNumber),
                                                              minimumVelocity: Int32(lowVelocity),
                                                              maximumVelocity: Int32(highVelocity),
                                                              isLooping: loopMode != "",
                                                              loopStartPoint: loopstart,
                                                              loopEndPoint: loopend,
                                                              startPoint: 0.0,
                                                              endPoint: 0.0)
                    sample = sample.replacingOccurrences(of: "\\", with: "/")
                    let sampleFileURL = samplesBaseURL
                        .appendingPathComponent(sample)
                    if sample.hasSuffix(".wv") {
                        sampleFileURL.path.withCString { path in
                            loadCompressedSampleFile(from: SampleFileDescriptor(sampleDescriptor: sampleDescriptor,
                                                                                  path: path))
                        }
                    } else {
                        if sample.hasSuffix(".aif") || sample.hasSuffix(".wav") {
                            let compressedFileURL = samplesBaseURL
                                .appendingPathComponent(String(sample.dropLast(4) + ".wv"))
                            let fileMgr = FileManager.default
                            if fileMgr.fileExists(atPath: compressedFileURL.path) {
                                compressedFileURL.path.withCString { path in
                                    loadCompressedSampleFile(
                                        from: SampleFileDescriptor(sampleDescriptor: sampleDescriptor,
                                                                     path: path))
                                }
                            } else {
                                let sampleFile = try AVAudioFile(forReading: sampleFileURL)
                                loadAudioFile(sampleDescriptor: sampleDescriptor, file: sampleFile)
                            }
                        }
                    }
                    sample = ""
                }
            }
        } catch {
            Log("Sampler \(error.localizedDescription)")
        }
    }
buildKeyMap()
}

I think my best chance is to use .wv instead of .wav so I can use loadCompressedSampleFile() which is public

I'm seeing this function in Sampler.swift
public func loadAudioFile(file: AVAudioFile, rootNote: UInt8 = 48, noteFrequency: Float = 440, loKey: UInt8 = 0, hiKey: UInt8 = 127, loVelocity: UInt8 = 0, hiVelocity: UInt8 = 127, startPoint: Float = 0, endPoint: Float? = nil, loopEnabled: Bool = false, loopStartPoint: Float = 0, loopEndPoint: Float? = nil)

I made that public a while ago

Yes but as it calls unloadAllSamples() it will only work for the last piano key sound

Right, it's for loading a single audio file. Sorry if I missed that requirement.

You could always just make the function you need public.

Really ? How ?

By editing the source file directly. I imagine you just change 'internal' to 'public'.

Ok Xcode was blocking it, but it's ok with external editor, thanks a lot

aure commented

I think it was made internal to avoid people calling it whenever I potentially running into thread safety issues and crashes. But yeah, the way to do this is to make a fork of this repo. Then point your SPM to your fork instead of this repo. Then you can edit it at will and even use it in other projects that require this edit.