AudioKit / DunneAudioKit

Sampler and Synth Instruments as well as Chorus, Flanger and Stereo Delay Effects for AudioKit
MIT License
45 stars 29 forks source link

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

Closed JanBurp closed 3 years ago

JanBurp commented 3 years ago

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

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

aure commented 3 years ago

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

JanBurp commented 3 years ago

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

gregoireLem commented 2 years ago

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

getdunne commented 2 years ago

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).

gregoireLem commented 2 years ago

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

getdunne commented 2 years ago

@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?

gregoireLem commented 2 years ago

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()
}
gregoireLem commented 2 years ago

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

eljeff commented 2 years ago

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

gregoireLem commented 2 years ago

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

eljeff commented 2 years ago

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.

gregoireLem commented 2 years ago

Really ? How ?

eljeff commented 2 years ago

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

gregoireLem commented 2 years ago

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

aure commented 2 years ago

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.