{"action":"created","issue":{"url":"https://api.github.com/repos/AudioKit/AudioKit/issues/1307","repository_url":"https://api.github.com/repos/AudioKit/AudioKit","labels_url":"https://api.github.com/repos/AudioKit/AudioKit/issues/1307/labels{/name}","comments_url":"https://api.github.com/repos/AudioKit/AudioKit/issues/1307/comments","events_url":"https://api.github.com/repos/AudioKit/AudioKit/issues/1307/events","html_url":"https://github.com/AudioKit/AudioKit/issues/1307","id":312145240,"number":1307,"title":"AKSampler with sfz","user":{"login":"jbmaxwell","id":15166432,"avatar_url":"https://avatars3.githubusercontent.com/u/15166432?v=4","gravatar_id":"","url":"https://api.github.com/users/jbmaxwell","html_url":"https://github.com/jbmaxwell","followers_url":"https://api.github.com/users/jbmaxwell/followers","following_url":"https://api.github.com/users/jbmaxwell/following{/other_user}","gists_url":"https://api.github.com/users/jbmaxwell/gists{/gist_id}","starred_url":"https://api.github.com/users/jbmaxwell/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jbmaxwell/subscriptions","organizations_url":"https://api.github.com/users/jbmaxwell/orgs","repos_url":"https://api.github.com/users/jbmaxwell/repos","events_url":"https://api.github.com/users/jbmaxwell/events{/privacy}","received_events_url":"https://api.github.com/users/jbmaxwell/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":{"login":"getdunne","id":4997153,"avatar_url":"https://avatars2.githubusercontent.com/u/4997153?v=4","gravatar_id":"","url":"https://api.github.com/users/getdunne","html_url":"https://github.com/getdunne","followers_url":"https://api.github.com/users/getdunne/followers","following_url":"https://api.github.com/users/getdunne/following{/other_user}","gists_url":"https://api.github.com/users/getdunne/gists{/gist_id}","starred_url":"https://api.github.com/users/getdunne/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/getdunne/subscriptions","organizations_url":"https://api.github.com/users/getdunne/orgs","repos_url":"https://api.github.com/users/getdunne/repos","events_url":"https://api.github.com/users/getdunne/events{/privacy}","received_events_url":"https://api.github.com/users/getdunne/received_events","type":"User","site_admin":false},"assignees":[{"login":"getdunne","id":4997153,"avatar_url":"https://avatars2.githubusercontent.com/u/4997153?v=4","gravatar_id":"","url":"https://api.github.com/users/getdunne","html_url":"https://github.com/getdunne","followers_url":"https://api.github.com/users/getdunne/followers","following_url":"https://api.github.com/users/getdunne/following{/other_user}","gists_url":"https://api.github.com/users/getdunne/gists{/gist_id}","starred_url":"https://api.github.com/users/getdunne/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/getdunne/subscriptions","organizations_url":"https://api.github.com/users/getdunne/orgs","repos_url":"https://api.github.com/users/getdunne/repos","events_url":"https://api.github.com/users/getdunne/events{/privacy}","received_events_url":"https://api.github.com/users/getdunne/received_events","type":"User","site_admin":false}],"milestone":null,"comments":2,"created_at":"2018-04-06T23:08:35Z","updated_at":"2018-04-07T01:59:39Z","closed_at":null,"author_association":"NONE","body":"I noticed a few issues loading sfz files from \"the wild\", and made a few tweaks to get them loading: \r\n\r\n    open func loadUsingSfzFile(folderPath: String, sfzFileName: String) {\r\n        let isRoland = true\r\n\r\n        /* Common <group> opcodes (Not yet handled?) */\r\n        var ampeg_attack: Float = 0.04\r\n        var ampeg_release: Float = 0.5\r\n        var amp_veltrack: Float = 75.0\r\n        \r\n        /* Common <region> opcodes */\r\n        var lokey: Int32 = 0\r\n        var hikey: Int32 = 127\r\n        var pitch: Int32 = 60\r\n        var lovel: Int32 = 0\r\n        var hivel: Int32 = 127\r\n        var sample: String = \"\"\r\n        var loopmode: String = \"\"\r\n        var loopstart: Float32 = 0\r\n        var loopend: Float32 = 0\r\n\r\n        let baseURL = URL(fileURLWithPath: folderPath)\r\n        let sfzURL = baseURL.appendingPathComponent(sfzFileName)\r\n        do {\r\n            let data = try String(contentsOf: sfzURL, encoding: .ascii)\r\n            let splitSet = Set(\"<\")\r\n            let lines = data.splitBefore(isSplit: splitSet.contains).map(String.init).map { $0.replacingOccurrences(of: \"\\\\\", with: \"/\") }\r\n            \r\n            for line in lines {\r\n                let trimmed = String(line.trimmingCharacters(in: .whitespacesAndNewlines))\r\n                if trimmed == \"\" || trimmed.hasPrefix(\"//\") {\r\n                    // ignore blank lines and comment lines\r\n                    continue\r\n                }\r\n                if trimmed.hasPrefix(\"<group>\") {\r\n                    // parse a <group> line\r\n                    lokey = 0\r\n                    hikey = 127\r\n                    pitch = 60\r\n                    lovel = 0\r\n                    hivel = 127\r\n                    for part in trimmed.dropFirst(7).components(separatedBy: .whitespacesAndNewlines) {\r\n                        if part.hasPrefix(\"key\") {\r\n                            let value = part.components(separatedBy: \"=\")[1]\r\n                            let midiNote = noteStringToMIDINote(value, isRoland: isRoland)\r\n                            pitch = Int32(midiNote)!\r\n                            lokey = pitch\r\n                            hikey = pitch\r\n                        } else if part.hasPrefix(\"lokey\") {\r\n                            let value = part.components(separatedBy: \"=\")[1]\r\n                            let midiNote = noteStringToMIDINote(value, isRoland: isRoland)\r\n                            lokey = Int32(midiNote)!\r\n                        } else if part.hasPrefix(\"hikey\") {\r\n                            let value = part.components(separatedBy: \"=\")[1]\r\n                            let midiNote = noteStringToMIDINote(value, isRoland: isRoland)\r\n                            hikey = Int32(midiNote)!\r\n                        } else if part.hasPrefix(\"pitch_keycenter\") {\r\n                            let value = part.components(separatedBy: \"=\")[1]\r\n                            let midiNote = noteStringToMIDINote(value, isRoland: isRoland)\r\n                            pitch = Int32(midiNote)!\r\n                        } else if part.hasPrefix(\"lovel\") {\r\n                            lovel = Int32(part.components(separatedBy: \"=\")[1])!\r\n                        } else if part.hasPrefix(\"hivel\") {\r\n                            hivel = Int32(part.components(separatedBy: \"=\")[1])!\r\n                        } else if part.hasPrefix(\"ampeg_attack\") {\r\n                            ampeg_attack = Float(part.components(separatedBy: \"=\")[1])!\r\n                        } else if part.hasPrefix(\"ampeg_release\") {\r\n                            ampeg_release = Float(part.components(separatedBy: \"=\")[1])!\r\n                        } else if part.hasPrefix(\"amp_veltrack\") {\r\n                            amp_veltrack = Float(part.components(separatedBy: \"=\")[1])!\r\n                        } else if part.hasPrefix(\"loop_mode\") {\r\n                            loopmode = part.components(separatedBy: \"=\")[1]\r\n                        }\r\n                    }\r\n                }\r\n                if trimmed.hasPrefix(\"<region>\") {\r\n                    // parse a <region> line\r\n                    lovel = 0\r\n                    hivel = 127\r\n                    sample = \"\"\r\n                    loopmode = \"\"\r\n                    loopstart = 0\r\n                    loopend = 0\r\n                    for part in trimmed.dropFirst(8).components(separatedBy: .whitespacesAndNewlines) {\r\n                        print(\"part: \\(part)\")\r\n                        if part.hasPrefix(\"key\") {\r\n                            let value = part.components(separatedBy: \"=\")[1]\r\n                            let midiNote = noteStringToMIDINote(value, isRoland: isRoland)\r\n                            pitch = Int32(midiNote)!\r\n                            lokey = pitch\r\n                            hikey = pitch\r\n                        } else if part.hasPrefix(\"lokey\") {\r\n                            let value = part.components(separatedBy: \"=\")[1]\r\n                            let midiNote = noteStringToMIDINote(value, isRoland: isRoland)\r\n                            lokey = Int32(midiNote)!\r\n                        } else if part.hasPrefix(\"hikey\") {\r\n                            let value = part.components(separatedBy: \"=\")[1]\r\n                            let midiNote = noteStringToMIDINote(value, isRoland: isRoland)\r\n                            hikey = Int32(midiNote)!\r\n                        } else if part.hasPrefix(\"pitch_keycenter\") {\r\n                            let value = part.components(separatedBy: \"=\")[1]\r\n                            let midiNote = noteStringToMIDINote(value, isRoland: isRoland)\r\n                            pitch = Int32(midiNote)!\r\n                        } else if part.hasPrefix(\"lovel\") {\r\n                            lovel = Int32(part.components(separatedBy: \"=\")[1])!\r\n                        } else if part.hasPrefix(\"hivel\") {\r\n                            hivel = Int32(part.components(separatedBy: \"=\")[1])!\r\n                        } else if part.hasPrefix(\"loop_mode\") {\r\n                            loopmode = part.components(separatedBy: \"=\")[1]\r\n                        } else if part.hasPrefix(\"loop_start\") {\r\n                            loopstart = Float32(part.components(separatedBy: \"=\")[1])!\r\n                        } else if part.hasPrefix(\"loop_end\") {\r\n                            loopend = Float32(part.components(separatedBy: \"=\")[1])!\r\n                        } else if part.hasPrefix(\"sample\") || sample.hasPrefix(\"sample\") {\r\n                            if !(part.contains(\".\") || sample.contains(\".\")) {\r\n                                if sample.count == 0 {\r\n                                    sample = part\r\n                                } else {\r\n                                    sample = \"\\(sample) \\(part)\"\r\n                                }\r\n                                continue\r\n                            }\r\n                            sample = \"\\(sample) \\(part)\"\r\n                            sample = sample.components(separatedBy: \"sample=\")[1]\r\n                        }\r\n                    }\r\n\r\n                    let noteFreq = Float(AKPolyphonicNode.tuningTable.frequency(forNoteNumber: MIDINoteNumber(pitch)))\r\n                    print(\"load \\(pitch) \\(noteFreq) Hz range \\(lokey)-\\(hikey) vel \\(lovel)-\\(hivel) \\(sample)\")\r\n\r\n                    let sd = AKSampleDescriptor(noteNumber: pitch,\r\n                                                noteHz: noteFreq,\r\n                                                min_note: lokey,\r\n                                                max_note: hikey,\r\n                                                min_vel: lovel,\r\n                                                max_vel: hivel,\r\n                                                bLoop: loopmode != \"\",\r\n                                                fLoopStart: loopstart,\r\n                                                fLoopEnd: loopend,\r\n                                                fStart: 0.0,\r\n                                                fEnd: 0.0)\r\n                    let sampleFileURL = baseURL.appendingPathComponent(sample)\r\n                    if sample.hasSuffix(\".wv\") {\r\n                        loadCompressedSampleFile(sfd: AKSampleFileDescriptor(sd: sd, path: sampleFileURL.path))\r\n                    } else {\r\n                        if sample.hasSuffix(\".aif\") || sample.hasSuffix(\".wav\") {\r\n                            let compressedFileURL = baseURL.appendingPathComponent(String(sample.dropLast(4) + \".wv\"))\r\n                            let fileMgr = FileManager.default\r\n                            if fileMgr.fileExists(atPath: compressedFileURL.path) {\r\n                                loadCompressedSampleFile(sfd: AKSampleFileDescriptor(sd: sd, path: compressedFileURL.path))\r\n                            } else {\r\n                                let sampleFile = try AKAudioFile(forReading: sampleFileURL)\r\n                                loadAKAudioFile(sd: sd, file: sampleFile)\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        } catch {\r\n            print(error)\r\n        }\r\n\r\n        buildKeyMap()\r\n    }\r\n\r\nSome of the parameters in the \"group\" handler are not commonly used in groups, though it seems that the exs converter puts them there, so I left those and added the more common \"group\" ones from the spec. With the above changes I managed to get this sfz loading: [http://freepats.zenvoid.org/Piano/acoustic-grand-piano.html#KawaiUpright](url), but playback is choppy. It sounds like it's cutting off any sustaining notes as soon as a new note is articulated. (Just to note, it is polyphonic, so it's not some voice-stealing thing.)"},"comment":{"url":"https://api.github.com/repos/AudioKit/AudioKit/issues/comments/379424332","html_url":"https://github.com/AudioKit/AudioKit/issues/1307#issuecomment-379424332","issue_url":"https://api.github.com/repos/AudioKit/AudioKit/issues/1307","id":379424332,"user":{"login":"getdunne","id":4997153,"avatar_url":"https://avatars2.githubusercontent.com/u/4997153?v=4","gravatar_id":"","url":"https://api.github.com/users/getdunne","html_url":"https://github.com/getdunne","followers_url":"https://api.github.com/users/getdunne/followers","following_url":"https://api.github.com/users/getdunne/following{/other_user}","gists_url":"https://api.github.com/users/getdunne/gists{/gist_id}","starred_url":"https://api.github.com/users/getdunne/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/getdunne/subscriptions","organizations_url":"https://api.github.com/users/getdunne/orgs","repos_url":"https://api.github.com/users/getdunne/repos","events_url":"https://api.github.com/users/getdunne/events{/privacy}","received_events_url":"https://api.github.com/users/getdunne/received_events","type":"User","site_admin":false},"created_at":"2018-04-07T01:59:39Z","updated_at":"2018-04-07T01:59:39Z","author_association":"MEMBER","body":"Hi JB, thanks for the feedback and especially for improvements to SFZ interpretation. There's potentially a huge amount of work to do there, and any help would by hugely appreciated! I'll have a look at the SFZ file you linked to, and see if I can figure out what's going wrong. My first thought is that samples might not be looping correctly, or maybe not at all."}}
{"actor":{"display_login":"getdunne"}}