EQ? #245
Replies: 51 comments 19 replies
-
|
You can add an equalizer implemented by an audio unit or AVAudioUnit (such as AVAudioUnitEQ) by adding it to the processing graph between |
Beta Was this translation helpful? Give feedback.
-
|
Assuming your app manages the class MyPlayerController {
let player: AudioPlayer
var equalizer: AVAudioUnitEffect! = nil
init() {
player = AudioPlayer()
// Insert 4-band equalizer between the player and main mixer nodes
player.withEngine { engine in
let mainMixer = engine.mainMixerNode
guard let mainMixerInputConnectionPoint = engine.inputConnectionPoint(for: mainMixer, inputBus: 0) else {
// Handle error
}
self.equalizer = AVAudioUnitEQ(numberOfBands: 4)
guard self.equalizer != nil else {
// Handle error
}
engine.attach(self.equalizer)
engine.disconnectNodeInput(mainMixer, bus: 0)
engine.connect(self.equalizer, to: mainMixer, format: nil)
guard let mainMixerInputNode = mainMixerInputConnectionPoint.node else {
// Handle error
}
engine.connect(mainMixerInputNode, to: self.equalizer, format: nil)
}
}
} |
Beta Was this translation helpful? Give feedback.
-
|
Looks good, I'll give it a go. Thanks! |
Beta Was this translation helpful? Give feedback.
-
|
Hi, trying to implement, but #import "SFBAudioPlayer.h" is not found. This is how the project looks like. Thanks! |
Beta Was this translation helpful? Give feedback.
-
|
Are you including the SFBAudioEngine framework in your target? If so try importing the umbrella header using |
Beta Was this translation helpful? Give feedback.
-
|
No and I don’t see SFB in the target of the simpleiOS App. I included a screenshot of both my project and the simpleiOS App.
Thanks for the help¡


… On Jun 12, 2023, at 12:16 PM, Stephen Booth ***@***.***> wrote:
Are you including the SFBAudioEngine framework in your target? If so try importing the umbrella header using #import <SFBAudioEngine/SFBAudioEngine.h>.
—
Reply to this email directly, view it on GitHub <#245 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABLTNEAYZG2L6BSKR5V55Z3XK5TIVANCNFSM6AAAAAASMEFLFY>.
You are receiving this because you authored the thread.
|
Beta Was this translation helpful? Give feedback.
-
|
I recommend building You can also use SimplePlayer as a template to see how to make your target dependent on the framework target in SFBAudioEngine's |
Beta Was this translation helpful? Give feedback.
-
|
I linked them. The errors stopped and the bridge-header recognizes SFB, but my main player; UniversallyPlayer.h doesn’t recognize the SFBAudioPlayer.h.
Thanks!



… On Jun 13, 2023, at 6:42 PM, Stephen Booth ***@***.***> wrote:
The UTType functions are defined in the CoreServices framework. Linking to that will help with those errors.
—
Reply to this email directly, view it on GitHub <#245 (reply in thread)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABLTNEEZTCYFHUAGJPYILS3XLEJIZANCNFSM6AAAAAASMEFLFY>.
You are receiving this because you authored the thread.
|
Beta Was this translation helpful? Give feedback.
-
|
Well I imported the #include <SFBAudioEngine/SFBAudioEngine.h>
And that worked! So now I have access to the player.
I’ll let you know if I am able to play a track.
THANKS!
… On Jun 13, 2023, at 6:42 PM, Stephen Booth ***@***.***> wrote:
The UTType functions are defined in the CoreServices framework. Linking to that will help with those errors.
—
Reply to this email directly, view it on GitHub <#245 (reply in thread)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABLTNEEZTCYFHUAGJPYILS3XLEJIZANCNFSM6AAAAAASMEFLFY>.
You are receiving this because you authored the thread.
|
Beta Was this translation helpful? Give feedback.
-
|
For some reason it stopped recognizing the #include <SFBAudioEngine/SFBAudioEngine.h>
I tried to import by dragging the entire folder into my project but I get the “Built External…” is it supposed to be an external built?
I followed this exactly as it is:
Building SFBAudioEngine
git clone https://github.com/sbooth/SFBAudioEngine.git --recurse-submodules
cd SFBAudioEngine
make -C XCFrameworks
Then dragged the entire folder onto my project.
… On Jun 13, 2023, at 6:42 PM, Stephen Booth ***@***.***> wrote:
The UTType functions are defined in the CoreServices framework. Linking to that will help with those errors.
—
Reply to this email directly, view it on GitHub <#245 (reply in thread)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABLTNEEZTCYFHUAGJPYILS3XLEJIZANCNFSM6AAAAAASMEFLFY>.
You are receiving this because you authored the thread.
|
Beta Was this translation helpful? Give feedback.
-
|
Yes. Now I have another programmer that is going to try to help me. I’ll let you know.Sent from my iPhoneOn Jun 15, 2023, at 8:27 AM, Stephen Booth ***@***.***> wrote:
Have you built the XCFramework like I suggested?
I recommend building SFBAudioEngine.xcframework as described in the README and then copying the resultant XCFramework to your project's directory. Then you can add it to your project like any other framework.
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you authored the thread.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
|
We’ve tried everything. We created a new project with practically nothing in it and we still get the same file not found error.
Can you let me know in what way you want us to import the engine?
If I put the engine in my projects folder, it auto appears in the project.
For iOS, you state:
"When compiling iOS targets against SFBAudioEngine it is necessary not only to link to and embed SFBAudioEngine.framework, but the XCFrameworks used by SFBAudioEngine as well since iOS does not support umbrella frameworks."
Can you clarify how you want to “link” as well as “embed” the engine.framework?
And how to do the same with the XCFramworks?
Because if I do what you are asking, I get a Dupplicate Framework error. And if I don’t, then I get the Not Found error.
Thanks, Dorian
… On Jun 15, 2023, at 8:27 AM, Stephen Booth ***@***.***> wrote:
Have you built the XCFramework like I suggested <#245 (comment)>?
I recommend building SFBAudioEngine.xcframework as described in the README <https://github.com/sbooth/SFBAudioEngine#building-sfbaudioengine> and then copying the resultant XCFramework to your project's directory. Then you can add it to your project like any other framework.
—
Reply to this email directly, view it on GitHub <#245 (reply in thread)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABLTNECO2TABPCVQFWXL5QDXLMSWNANCNFSM6AAAAAASMEFLFY>.
You are receiving this because you authored the thread.
|
Beta Was this translation helpful? Give feedback.
-
|
I linked it via the project built phase and now there are many errors.

… On Jun 15, 2023, at 8:27 AM, Stephen Booth ***@***.***> wrote:
Have you built the XCFramework like I suggested <#245 (comment)>?
I recommend building SFBAudioEngine.xcframework as described in the README <https://github.com/sbooth/SFBAudioEngine#building-sfbaudioengine> and then copying the resultant XCFramework to your project's directory. Then you can add it to your project like any other framework.
—
Reply to this email directly, view it on GitHub <#245 (reply in thread)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABLTNECO2TABPCVQFWXL5QDXLMSWNANCNFSM6AAAAAASMEFLFY>.
You are receiving this because you authored the thread.
|
Beta Was this translation helpful? Give feedback.
-
|
This discussion has definitely gone off-topic from the initial question about equalization. Are you compiling for macOS or iOS? Did you try using the SimplePlayer projects as references on how to get things working? Please create a repository showing the problem because at this point I honestly don't understand what you're trying to do. |
Beta Was this translation helpful? Give feedback.
-
|
I have an iOS music player project that uses the STKPlayer library, which works and has EQ, but can’t play DSD files, so I want to use this player instead. I also want to make the app Mac compatible, and this player has both samples, so it looks like a perfect fit,
Using the sample iOS and Mac work, but if I bring it into the project, then it doesn’t.
… On Jun 17, 2023, at 9:20 AM, Stephen Booth ***@***.***> wrote:
This discussion has definitely gone off-topic from the initial question about equalization.
Are you compiling for macOS or iOS? Did you try using the SimplePlayer projects as references on how to get things working?
Please create a repository showing the problem because at this point I honestly don't understand what you're trying to do.
—
Reply to this email directly, view it on GitHub <#245 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABLTNEEJZAXAS3BDKAK6C7DXLXKMHANCNFSM6AAAAAASMEFLFY>.
You are receiving this because you authored the thread.
|
Beta Was this translation helpful? Give feedback.
-
|
So SFB wouldn’t contribute to the RAM getting used up? Any settings that we are not using or something else?We are using Realm, and even if I set it to saving just three fields: songID, songName, and songPath, the app uses RAM at about a gig per 1,000 songs.We are still looking for a solution, but we can’t seem to find what is using the RAM.Any pointers would help.Thanks!Sent from my iPhoneOn Oct 2, 2023, at 9:41 PM, Stephen Booth ***@***.***> wrote:
Handling large libraries is a broad topic with no ready-made solution that I'm aware of. You'd likely need a database (Core Data, SQLite, or something else) to manage the data efficiently. What you store in the database (and how) would depend on your app.
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you authored the thread.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
|
Hi Stephen,
Here is a screenshot.

… On May 28, 2024, at 6:06 PM, Stephen Booth ***@***.***> wrote:
I recommend using Instruments to profile the memory usage, it should tell you quite a bit about the allocations.
—
Reply to this email directly, view it on GitHub <#245 (reply in thread)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABLTNEDB4MRVXTFMNTA46SLZET5WPAVCNFSM6AAAAAASMEFLF2VHI2DSMVQWIX3LMV43SRDJONRXK43TNFXW4Q3PNVWWK3TUHM4TKOBWGIZTI>.
You are receiving this because you authored the thread.
|
Beta Was this translation helpful? Give feedback.
-
|
We found that the issue is the way SFB loads all the images to ram. If we leave out the images, Then the app uses very little ram.
How do we tell SFB to not load the images to ram and have it lazy load?
We can save them to the drive and lazy load them.
Thanks,
Dorian
… On May 28, 2024, at 6:06 PM, Stephen Booth ***@***.***> wrote:
I recommend using Instruments to profile the memory usage, it should tell you quite a bit about the allocations.
—
Reply to this email directly, view it on GitHub <#245 (reply in thread)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABLTNEDB4MRVXTFMNTA46SLZET5WPAVCNFSM6AAAAAASMEFLF2VHI2DSMVQWIX3LMV43SRDJONRXK43TNFXW4Q3PNVWWK3TUHM4TKOBWGIZTI>.
You are receiving this because you authored the thread.
|
Beta Was this translation helpful? Give feedback.
-
|
I'm not sure I understand your setup. Are you instantiating a metadata instance for each file in the library? That class should be used to read the metadata and album art from the file, and then it should be copied into some other data store that can handle loading and paging. You could also try getting the dictionary representation and then stripping the album art from that. |
Beta Was this translation helpful? Give feedback.
-
|
Hi, thanks for the response.I am now able to strip the artwork and retrieve it via url. But at startup it consumes a lot of ram.I’m looking to see how I can manage that process.Any ideas for SFB at startup?Sent from my iPhoneOn Jun 2, 2024, at 7:06 AM, Stephen Booth ***@***.***> wrote:
I'm not sure I understand your setup. Are you instantiating a metadata instance for each file in the library? That class should be used to read the metadata and album art from the file, and then it should be copied into some other data store that can handle loading and paging. You could also try getting the dictionary representation and then stripping the album art from that.
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you authored the thread.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
|
I recommend only reading the metadata once when the file is initially added to your data store and then storing the fields you're interested in. Creating a new instance of metadata for every file at every app launch is going to be fairly slow because of the overhead involved in opening and parsing the files. |
Beta Was this translation helpful? Give feedback.
-
|
Yes, with the help of AI I was able to figure out how to save to realm and now it used 350MB approx. Thanks for the help!! I should have this app ready in a couple of months.Sent from my iPhoneOn Jun 13, 2024, at 9:34 PM, Stephen Booth ***@***.***> wrote:
I recommend only reading the metadata once when the file is initially added to your data store and then storing the fields you're interested in. Creating a new instance of metadata for every file at every app launch is going to be fairly slow because of the overhead involved in opening and parsing the files.
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you authored the thread.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
|
I ended up using another library for DSD playback, because SFB doesn’t play 512. I’m using BassDSD for Mac.Sent from my iPhoneOn Jul 1, 2024, at 9:26 AM, lanshanzhao ***@***.***> wrote:
Deer developer this eq add which file?
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you authored the thread.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
|
I am not being able to save the metadata: Is there a limitation for AIFF?
Any ideas?
Thanks!
10.28.1 - [FirebaseCore][I-COR000033] Data Collection flag is not set.
Return key pressed. Saving changes.
Text field updated: Doro
Updated track details:
Song Name: In Freiheit Stirbt Mein Herz [Single Edit]
Album Name: The Ballads
Artist Name: Doro
Genre: Metal
Release Date: 1995
Updated Metadata before writing:
Title: nil
Album Title: nil
Artist: nil
Genre: nil
Release Date: nil
Album Artist: nil
Composer: nil
Grouping: nil
Track Number: nil
Total Tracks: nil
Disc Number: nil
Total Discs: nil
Is Compilation: nil
*** Assertion failure in void SFB::Audio::SetID3v2TagFromMetadata(SFBAudioMetadata *__strong _Nonnull, TagLib::ID3v2::Tag * _Nonnull, bool)(), SFBAudioMetadata+TagLibID3v2Tag.mm:277
Uncaught exception: Invalid parameter not satisfying: metadata != nil
private func loadAlbumMetadata() {
guard let albumTracks = albumTracks else { return }
guard let track = albumTracks.first else { return }
debugPrint("1 track.imagePath", track.imagePath as Any)
sharedArtistName = track.artistName
sharedAlbumName = track.albumName
var artistName = track.artistName
var albumArtistName = track.artistFirstName ?? ""
var albumComposer = track.albumComposer ?? ""
var grouping = track.grouping
var fileType = track.fileFormat
var songDuration: Double? = track.songLenght
var averageVolume: Double? = track.loudness
var dateAdded = track.dateSongAdded
var dateModified = track.dateSongModified
var genre = track.genre
var releaseDate = track.releaseDate
var fileSize: UInt64? = track.fileSize
var isACompilation = track.isACompilation
for t in albumTracks.dropFirst() {
if t.artistName != artistName { artistName = "Various" }
if t.artistFirstName != albumArtistName { albumArtistName = "Various" }
if t.albumComposer != albumComposer { albumComposer = "Various" }
if t.grouping != grouping { grouping = "Various" }
if t.fileFormat != fileType { fileType = "Various" }
if t.songLenght != songDuration { songDuration = nil }
if t.loudness != averageVolume { averageVolume = nil }
if t.dateSongAdded != dateAdded { dateAdded = "Various" }
if t.dateSongModified != dateModified { dateModified = "Various" }
if t.genre != genre { genre = "Various" }
if t.releaseDate != releaseDate { releaseDate = "Various" }
if t.fileSize != fileSize { fileSize = nil }
if t.isACompilation != isACompilation { isACompilation = nil }
}
songNameLabel?.isEnabled = false
songNameLabel?.stringValue = "Album: \(track.albumName)"
artistsNameLabel?.stringValue = artistName
albumNameLabel?.stringValue = track.albumName
albumArtistNameLabel?.stringValue = albumArtistName
composerLabel?.stringValue = albumComposer
groupingLabel?.stringValue = grouping
fileTypeLabel?.stringValue = fileType
songDurationLabel?.isEnabled = false
songDurationLabel?.stringValue = (songDuration == nil) ? "Various" : "\(songDuration!)"
averageVolumeLabel?.isEnabled = false
averageVolumeLabel?.stringValue = (averageVolume == nil) ? "Various" : "\(averageVolume!)"
dateAddedLabel?.stringValue = dateAdded
fileLocationLabel?.isEnabled = false
fileLocationLabel?.stringValue = "Various"
dateModifiedLabel?.stringValue = dateModified
trackNumLabel?.isEnabled = false
trackOfLabel?.integerValue = track.numberOfTracks
discNumLabel?.isEnabled = false
discOfLabel?.integerValue = track.numberOfDiscs
genreComboBox?.stringValue = genre
yearComboBox?.stringValue = releaseDate
if albumTracks.isEmpty {
albumCompilationCheckBox?.state = .mixed
return
}
// Assume the first track's compilation status as the base
let firstCompilationStatus = albumTracks.first?.isACompilation
// Check if all tracks have the same compilation status
let allSame = albumTracks.allSatisfy { $0.isACompilation == firstCompilationStatus }
if allSame {
if let isACompilation = firstCompilationStatus {
albumCompilationCheckBox?.state = isACompilation ? .on : .off
} else {
albumCompilationCheckBox?.state = .mixed
}
} else {
albumCompilationCheckBox?.state = .mixed
}
fileSizeLabel?.isEnabled = false
fileSizeLabel?.stringValue = (fileSize == nil) ? "Various" : ByteCountFormatter.string(fromByteCount: Int64(fileSize!), countStyle: .file)
debugPrint("track.imagePath", track.imagePath as Any)
if let imagePath = track.imagePath, let image = NSImage(contentsOfFile: imagePath) {
albumImageView?.image = image
} else {
albumImageView?.image = nil
}
}
private func loadMetadataFromCurrentTrack() {
guard isViewLoaded else {
print("View is not loaded yet")
return
}
guard let track = currentTrack else {
print("currentTrack is nil")
return
}
isEditingMetadata = true
songNameLabel?.stringValue = track.songName
artistsNameLabel?.stringValue = track.artistName
albumNameLabel?.stringValue = track.albumName
albumArtistNameLabel?.stringValue = track.artistFirstName ?? ""
composerLabel?.stringValue = track.albumComposer ?? ""
groupingLabel?.stringValue = track.grouping
fileTypeLabel?.stringValue = track.fileFormat
songDurationLabel?.doubleValue = track.songLenght
averageVolumeLabel?.doubleValue = track.loudness
dateAddedLabel?.stringValue = track.dateSongAdded
dateModifiedLabel?.stringValue = track.dateSongModified
fileLocationLabel?.stringValue = track.sSongPath ?? ""
genreComboBox?.stringValue = track.genre
yearComboBox?.stringValue = track.releaseDate
trackNumLabel?.integerValue = track.trackNumber
trackOfLabel?.integerValue = track.numberOfTracks
discNumLabel?.integerValue = track.discNumber
discOfLabel?.integerValue = track.numberOfDiscs
// Set the state of the albumCompilationCheckBox
if let isACompilation = track.isACompilation {
albumCompilationCheckBox?.state = isACompilation ? .on : .off
} else {
albumCompilationCheckBox?.state = .mixed
}
if let fileSize = track.fileSize {
fileSizeLabel?.stringValue = ByteCountFormatter.string(fromByteCount: Int64(fileSize), countStyle: .file)
} else {
fileSizeLabel?.stringValue = "Unknown"
}
if let imagePath = track.imagePath, let image = NSImage(contentsOfFile: imagePath) {
albumImageView.image = image
} else {
albumImageView.image = nil
}
}
@objc func metadataFieldDidChange(_ sender: NSTextField) {
let newValue = sender.stringValue
print("Text field updated: \(newValue)")
// Determine if we're updating a single track or all album tracks
if var track = currentTrack {
// Single track: Update the corresponding metadata property
updateMetadata(for: &track, with: newValue, sender: sender)
// Log updated currentTrack
logUpdatedTrack(track)
// Save updated metadata
updateTrack(&track) { success in
if success {
self.saveCurrentTrackToRealm()
} else {
print("Failed to update metadata, so not saving to Realm.")
}
}
currentTrack = track // Re-assign the modified track back to currentTrack
} else if var albumTracks = albumTracks {
// Album: Update metadata for all tracks
for index in albumTracks.indices {
updateMetadata(for: &albumTracks[index], with: newValue, sender: sender)
}
// Save updated metadata for each track
for index in albumTracks.indices {
logUpdatedTrack(albumTracks[index])
updateTrack(&albumTracks[index]) { success in
if success {
self.saveCurrentTrackToRealm()
} else {
print("Failed to update metadata for album track \(index), so not saving to Realm.")
}
}
}
}
}
// Helper function to update metadata
private func updateMetadata(for track: inout CurrentTrack, with newValue: String, sender: NSTextField) {
switch sender {
case songNameLabel:
track.songName = newValue
case albumNameLabel:
track.albumName = newValue
case artistsNameLabel:
track.artistName = newValue
case albumArtistNameLabel:
track.artistFirstName = newValue
case composerLabel:
track.albumComposer = newValue
case groupingLabel:
track.grouping = newValue
case trackNumLabel:
track.trackNumber = Int(newValue) ?? 0
case trackOfLabel:
track.numberOfTracks = Int(newValue) ?? 0
case discNumLabel:
track.discNumber = Int(newValue) ?? 0
case discOfLabel:
track.numberOfDiscs = Int(newValue) ?? 0
default:
break
}
}
// Helper function to log updated track details
private func logUpdatedTrack(_ track: CurrentTrack) {
print("Updated track details:")
print("Song Name: \(track.songName)")
print("Album Name: \(track.albumName)")
print("Artist Name: \(track.artistName)")
print("Genre: \(track.genre)")
print("Release Date: \(track.releaseDate)")
}
@IBAction func albumCompilationCheckPressed(_ sender: Any) {
let newState = albumCompilationCheckBox?.state == .on
// Update the checkbox state and the currentTrack's isACompilation property
albumCompilationCheckBox?.state = newState ? .on : .off
currentTrack?.isACompilation = newState // Convert the state to a Bool
// Update isACompilation for all tracks in the album if it's an album
if var albumTracks = albumTracks {
for index in albumTracks.indices {
albumTracks[index].isACompilation = newState
}
}
// Save changes to Realm
saveCurrentTrackToRealm()
}
@IBAction func genreComboBoxDidChangeButtonAction(_ sender: Any) {
if let comboBox = sender as? NSComboBox {
let selectedValue = comboBox.stringValue
genreComboBox?.stringValue = selectedValue
if var track = currentTrack {
// For a single track, update genre metadata
track.genre = selectedValue
print("Updating genre for single track.")
updateTrack(&track) { success in
if success {
self.saveCurrentTrackToRealm()
} else {
print("Failed to update metadata for single track.")
}
}
} else if var albumTracks = albumTracks {
// For albums, update all tracks in the album
print("Updating genre for all album tracks.")
for index in albumTracks.indices {
albumTracks[index].genre = selectedValue
}
// Save metadata for all tracks
for index in albumTracks.indices {
updateTrack(&albumTracks[index]) { success in
if success {
self.saveCurrentTrackToRealm()
} else {
print("Failed to update metadata for album track \(index).")
}
}
}
}
}
}
@IBAction func yearComboBoxDidChangeButtonAction(_ sender: Any) {
if let comboBox = sender as? NSComboBox {
let selectedValue = comboBox.stringValue
yearComboBox?.stringValue = selectedValue
if let year = Int(selectedValue) {
if var track = currentTrack {
// For a single track, update release date metadata
track.releaseDate = String(year)
print("Updating release date for single track.")
updateTrack(&track) { success in
if success {
self.saveCurrentTrackToRealm()
} else {
print("Failed to update metadata for single track.")
}
}
} else if var albumTracks = albumTracks {
// For albums, update all tracks in the album
print("Updating release date for all album tracks.")
for index in albumTracks.indices {
albumTracks[index].releaseDate = String(year)
}
// Save metadata for all tracks
for index in albumTracks.indices {
updateTrack(&albumTracks[index]) { success in
if success {
self.saveCurrentTrackToRealm()
} else {
print("Failed to update metadata for album track \(index).")
}
}
}
}
}
}
}
// Function to update track metadata and save artwork
func updateTrack(_ track: inout CurrentTrack, completion: @escaping (Bool) -> Void) {
// Ensure we have a valid existing song in Realm
guard let audioFile = try? AudioFile(url: track.url) else {
print("Failed to create SFBAudioFile instance for track: \(track.songName) at URL: \(track.url)")
completion(false)
return
}
// Directly access the metadata
let metadata = audioFile.metadata
// Update the metadata with the new track details
metadata.title = track.songName
metadata.albumTitle = track.albumName
metadata.artist = track.artistName
metadata.genre = track.genre
metadata.releaseDate = track.releaseDate
metadata.albumArtist = track.artistFirstName ?? "" // Updating artistFirstName as albumArtist
metadata.composer = track.albumComposer ?? "" // Updating album composer
metadata.grouping = track.grouping // Updating grouping
metadata.trackNumber = track.trackNumber // Updating track number
metadata.trackTotal = track.numberOfTracks // Updating total number of tracks
metadata.discNumber = track.discNumber // Updating disc number
metadata.discTotal = track.numberOfDiscs // Updating total number of discs
metadata.isCompilation = track.isACompilation ?? false // Updating compilation status
// Log the updated metadata before writing it
print("Updated Metadata before writing:")
print("Title: \(String(describing: metadata.title))")
print("Album Title: \(String(describing: metadata.albumTitle))")
print("Artist: \(String(describing: metadata.artist))")
print("Genre: \(String(describing: metadata.genre))")
print("Release Date: \(String(describing: metadata.releaseDate))")
print("Album Artist: \(String(describing: metadata.albumArtist))")
print("Composer: \(String(describing: metadata.composer))")
print("Grouping: \(String(describing: metadata.grouping))")
print("Track Number: \(String(describing: metadata.trackNumber))")
print("Total Tracks: \(String(describing: metadata.trackTotal))")
print("Disc Number: \(String(describing: metadata.discNumber))")
print("Total Discs: \(String(describing: metadata.discTotal))")
print("Is Compilation: \(String(describing: metadata.isCompilation))")
do {
// Attempt to write the metadata
try audioFile.writeMetadata()
print("Successfully saved metadata for track: \(track.songName)")
completion(true)
} catch {
print("Failed to save metadata for track: \(track.songName): \(error)")
completion(false)
}
}
func saveCurrentTrackToRealm() {
let realm = try! Realm()
do {
try realm.write {
if let albumTracks = albumTracks {
// Save all album tracks to Realm
for track in albumTracks {
if let existingSong = realm.object(ofType: RealmDBAllSongs.self, forPrimaryKey: track.sSongId) {
existingSong.sAlbumName = track.albumName
existingSong.sArtistName = track.artistName
existingSong.sArtistFirstName = track.artistFirstName ?? ""
existingSong.sGenre = track.genre
existingSong.sYear = track.releaseDate
existingSong.sAlbumComposer = track.albumComposer ?? ""
existingSong.sGrouping = track.grouping
existingSong.sDateModified = Date.getCurrentDate()
existingSong.sTrackNumber = track.trackNumber
existingSong.sNumberOfTracks = track.numberOfTracks
existingSong.sDiscNumber = track.discNumber
existingSong.sNumberOfDiscs = track.numberOfDiscs
existingSong.isACompilation = track.isACompilation ?? false
}
}
print("All album tracks saved to Realm")
} else if let currentTrack = currentTrack {
// Save the single track to Realm
if let existingSong = realm.object(ofType: RealmDBAllSongs.self, forPrimaryKey: currentTrack.sSongId) {
existingSong.sAlbumName = currentTrack.albumName
existingSong.sName = currentTrack.songName
existingSong.sArtistName = currentTrack.artistName
existingSong.sArtistFirstName = currentTrack.artistFirstName ?? ""
existingSong.sGenre = currentTrack.genre
existingSong.sYear = currentTrack.releaseDate
existingSong.sAlbumComposer = currentTrack.albumComposer ?? ""
existingSong.sGrouping = currentTrack.grouping
existingSong.sDateModified = Date.getCurrentDate()
existingSong.sTrackNumber = currentTrack.trackNumber
existingSong.sNumberOfTracks = currentTrack.numberOfTracks
existingSong.sDiscNumber = currentTrack.discNumber
existingSong.sNumberOfDiscs = currentTrack.numberOfDiscs
print("Current track saved to Realm")
}
}
}
} catch {
print("Failed to write to Realm: \(error)")
}
}
… On Jun 13, 2024, at 10:16 PM, Dorian Mattar ***@***.***> wrote:
Yes, with the help of AI I was able to figure out how to save to realm and now it used 350MB approx.
Thanks for the help!! I should have this app ready in a couple of months.
Sent from my iPhone
> On Jun 13, 2024, at 9:34 PM, Stephen Booth ***@***.***> wrote:
>
>
>
> I recommend only reading the metadata once when the file is initially added to your data store and then storing the fields you're interested in. Creating a new instance of metadata for every file at every app launch is going to be fairly slow because of the overhead involved in opening and parsing the files.
>
> —
> Reply to this email directly, view it on GitHub <#245 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABLTNEFKJ4ABRKFIGIAWHWTZHJCBTAVCNFSM6AAAAAASMEFLF2VHI2DSMVQWIX3LMV43SRDJONRXK43TNFXW4Q3PNVWWK3TUHM4TONRZGE3DS>.
> You are receiving this because you authored the thread.
>
|
Beta Was this translation helpful? Give feedback.
-
|
Ok, now that I know that, we’ll figure it out. Thanks a million!Sent from my iPhoneOn Sep 12, 2024, at 11:32 AM, Stephen Booth ***@***.***> wrote:
Metadata for AIFF is supported but this is a bug in AudioFile. The internal metadata and properties objects aren't initialized until readMetadata is called, which you aren't doing in updateTrack which means the internal objects are empty leading to the assertion failure. I will work on a fix.
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you authored the thread.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
|
Hi, I’m trying to get airplay to work within an airplay popup in the app. Our menu opens fine, and after selecting an airplay device, I’m able to control the volume on the device, but the actual audio output stays in the “build-in output”. If I select the same device from the apple sound menu, then it shows along with the "build-in output” in the menu.
Any ideas?
// MARK: - Device Selection Action
@IBAction func audioOutputSelector(_ sender: AnyObject?) {
guard let menuItem = sender as? NSMenuItem, let device = menuItem.representedObject as? AudioDevice else {
return
}
var dicOutputAudio = [String: AudioObjectID]()
dicOutputAudio["outPutAudio"] = device.objectID
HardwareChange.shared.notifyobserver(objUpdateState: .UpdateOutputDevice)
NotificationCenter.default.post(name: kUpdateOutputAudioDevice, object: dicOutputAudio)
}
// MARK: - Device Menu and AirPlay Setup
@objc func loadAudioDeviceHardware() {
// Monitor for system device changes
try? AudioSystemObject.instance.whenSelectorChanges(.devices) { _ in
DispatchQueue.main.async {
self.updateDeviceMenu()
}
}
// Load previously selected output device
if let uid = UserDefaults.standard.object(forKey: "deviceUID") as? String,
let deviceID = try? AudioSystemObject.instance.deviceID(forUID: uid) {
try? player.setOutputDeviceID(deviceID)
}
// Update device menu and setup AirPlay button
updateDeviceMenu()
}
@objc func updateDeviceMenu() {
devicePopUpButton.menu?.removeAllItems()
do {
let currentOutputDeviceID = player.outputDeviceID
outputDevices = try AudioDevice.devices.filter { try $0.supportsOutput }
for outputDevice in outputDevices {
if let isPrivateAggregate = try (outputDevice as? AudioAggregateDevice)?.isPrivate, isPrivateAggregate {
continue
}
let isActiveDevice = outputDevice.objectID == currentOutputDeviceID
let deviceMenuItem = NSMenuItem(
title: try outputDevice.name,
action: #selector(self.audioOutputSelector(_:)),
keyEquivalent: ""
)
deviceMenuItem.target = self
deviceMenuItem.representedObject = outputDevice
deviceMenuItem.state = isActiveDevice ? .on : .off
devicePopUpButton.menu?.addItem(deviceMenuItem)
if isActiveDevice {
devicePopUpButton.select(deviceMenuItem)
}
dacInfoLabel.stringValue = try outputDevice.deviceUID
print("Device is Available, switching to device:", dacInfoLabel.stringValue)
}
devicePopUpButton.menu?.addItem(NSMenuItem.separator())
} catch let error {
print("Device Not Available, switching to next available device.", error)
}
}
// MARK: - AirPlay Button Setup
@objc func setupAirPlayButton() {
// Initialize and configure the AirPlay picker view
let airPlayPickerView = AVRoutePickerView()
airPlayPickerView.translatesAutoresizingMaskIntoConstraints = false
// Container view to house AirPlay picker
let airPlayContainerView = NSView()
airPlayContainerView.translatesAutoresizingMaskIntoConstraints = false
airPlayContainerView.addSubview(airPlayPickerView)
// Add AirPlay picker to view hierarchy
view.addSubview(airPlayContainerView)
NSLayoutConstraint.activate([
airPlayPickerView.widthAnchor.constraint(equalToConstant: 18),
airPlayPickerView.heightAnchor.constraint(equalToConstant: 18),
airPlayPickerView.centerXAnchor.constraint(equalTo: airPlayContainerView.centerXAnchor),
airPlayPickerView.centerYAnchor.constraint(equalTo: airPlayContainerView.centerYAnchor),
airPlayContainerView.leadingAnchor.constraint(equalTo: devicePopUpButton.trailingAnchor, constant: 6),
airPlayContainerView.centerYAnchor.constraint(equalTo: devicePopUpButton.centerYAnchor),
airPlayContainerView.widthAnchor.constraint(equalToConstant: 30),
airPlayContainerView.heightAnchor.constraint(equalToConstant: 30)
])
}
… On Sep 12, 2024, at 11:32 AM, Stephen Booth ***@***.***> wrote:
Metadata for AIFF is supported but this is a bug in AudioFile. The internal metadata and properties objects aren't initialized until readMetadata is called, which you aren't doing in updateTrack which means the internal objects are empty leading to the assertion failure. I will work on a fix.
—
Reply to this email directly, view it on GitHub <#245 (reply in thread)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABLTNEBLTLTYVCSXZERJDBDZWGXZBAVCNFSM6AAAAAASMEFLF2VHI2DSMVQWIX3LMV43URDJONRXK43TNFXW4Q3PNVWWK3TUHMYTANRSG44DCNQ>.
You are receiving this because you authored the thread.
|
Beta Was this translation helpful? Give feedback.
-
|
Ok, I am trying to play Apple Music, but I can’t seem to be able to switch the output so that apple can use the output and then switch back to SFB when I’m playing local songs.
Any ideas?
Thanks!
… On Nov 5, 2024, at 5:25 PM, Stephen Booth ***@***.***> wrote:
I don't believe Apple provides a way to programmatically enumerate AirPlay devices and select one via device ID. This used to be possible in macOS but they removed that capability. I think iOS is the same way unfortunately.
—
Reply to this email directly, view it on GitHub <#245 (reply in thread)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABLTNEDBBFORUJDRSXMPMGLZ7EZUZAVCNFSM6AAAAAASMEFLF2VHI2DSMVQWIX3LMV43URDJONRXK43TNFXW4Q3PNVWWK3TUHMYTCMJVHE3DSMQ>.
You are receiving this because you authored the thread.
|
Beta Was this translation helpful? Give feedback.
-
|
Hi, we’re just about rapping up the dev cycle, and we’re trying to submit to Apple, but we are getting validation errors that seem to be aiming at SFB.
Any Ideas?
Thanks!

… On Dec 10, 2024, at 12:55 PM, Dorian Mattar ***@***.***> wrote:
Ok, I am trying to play Apple Music, but I can’t seem to be able to switch the output so that apple can use the output and then switch back to SFB when I’m playing local songs.
Any ideas?
Thanks!
> On Nov 5, 2024, at 5:25 PM, Stephen Booth ***@***.***> wrote:
>
>
> I don't believe Apple provides a way to programmatically enumerate AirPlay devices and select one via device ID. This used to be possible in macOS but they removed that capability. I think iOS is the same way unfortunately.
>
> —
> Reply to this email directly, view it on GitHub <#245 (reply in thread)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABLTNEDBBFORUJDRSXMPMGLZ7EZUZAVCNFSM6AAAAAASMEFLF2VHI2DSMVQWIX3LMV43URDJONRXK43TNFXW4Q3PNVWWK3TUHMYTCMJVHE3DSMQ>.
> You are receiving this because you authored the thread.
>
|
Beta Was this translation helpful? Give feedback.
-
|
Let me ask my partner, because from what he told me, I understood that these screenshots showed that SFB was the issue, but I’m looking at that and all I see is i386 issues. Looks like something in the settings is allowing 32bit instead of 64bit, or read/write permission which are in the plist.
I’ll let you know if we still need help. Thanks for the quick response. Once we release, can I send you a code so that you can enjoy the app for life for FREE? It’s the least we can do for you!
Thanks!
… On Mar 6, 2025, at 6:29 PM, Stephen Booth ***@***.***> wrote:
What errors are you getting that make you suspect SFBAudioEngine?
—
Reply to this email directly, view it on GitHub <#245 (reply in thread)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABLTNEAQFMMS3KKWNGWSOKD2TDD3JAVCNFSM6AAAAAASMEFLF2VHI2DSMVQWIX3LMV43URDJONRXK43TNFXW4Q3PNVWWK3TUHMYTENBRHE2TEMY>.
You are receiving this because you authored the thread.
|
Beta Was this translation helpful? Give feedback.
-
|
Hi, I’m getting a crash if MediaCenter or another music player that hogs the audio device is active when our app starts.
This is our code: // MARK: - Load Audio Device Hardware
@objc func loadAudioDeviceHardware() {
airPlayPickerView.delegate = self
// Observe when audio devices change
try? AudioSystemObject.instance.whenSelectorChanges(.devices) { _ in
DispatchQueue.main.async {
self.updateDeviceMenu()
}
}
print("Audio system object selector changes observer set up.")
// Retrieve the saved device UID and set the output device
if let uid = UserDefaults.standard.object(forKey: "deviceUID") as? String {
do {
// Fetch the audio device using the UID
if let deviceID = try AudioSystemObject.instance.deviceID(forUID: uid) {
// Check if the device is hogged by another application
try checkAudioDeviceStatus()
// Set the device if it's available
try? sfbAudioDevice.setOutputDeviceID(deviceID)
print("Audio device selected with UID:", uid)
}
} catch {
print("Error loading or checking the device:", error)
}
}
updateDeviceMenu()
}
// MARK: - Check Audio Device Status (if hogged)
func checkAudioDeviceStatus() throws {
// Safely retrieve the default output device
guard let currentDevice = try? AudioDevice.defaultOutputDevice else {
// If the device is unavailable, show an alert or handle it accordingly
let alert = NSAlert()
alert.messageText = "Audio Device Not Found"
alert.informativeText = "Unable to access the default audio device. Please ensure your device is connected properly."
alert.addButton(withTitle: "OK")
alert.runModal()
return
}
// If the device was found, check if it is hogged
do {
if try currentDevice.isHogged {
// Device is hogged, show an alert to the user
let alert = NSAlert()
alert.messageText = "Audio Device In Use"
alert.informativeText = "Audio Device is currently used by another application. Please release the audio device and try again."
alert.addButton(withTitle: "OK")
alert.runModal()
} else {
print("Audio device is available for use.")
}
} catch {
// Handle any errors that may occur while checking the hog status
let alert = NSAlert()
alert.messageText = "Error Checking Device Status"
alert.informativeText = "An error occurred while checking the audio device's status. Please try again."
alert.addButton(withTitle: "OK")
alert.runModal()
}
}
Thread 1: Precondition failed
func makeAudioDevice(_ objectID: AudioObjectID) throws -> AudioDevice {
precondition(objectID != kAudioObjectUnknown)
precondition(objectID != kAudioObjectSystemObject)
let objectClass = try AudioObjectClass(objectID)
switch objectClass {
case kAudioDeviceClassID: return AudioDevice(objectID)
case kAudioAggregateDeviceClassID: return AudioAggregateDevice(objectID)
case kAudioEndPointDeviceClassID: return AudioEndpointDevice(objectID)
case kAudioEndPointClassID: return AudioEndpoint(objectID)
case kAudioSubDeviceClassID: return AudioSubdevice(objectID)
default:
os_log(.debug, log: audioObjectLog, "Unknown audio device class '%{public}@' for audio object 0x%{public}@", objectClass.fourCC, objectID.hexString)
return AudioDevice(objectID)
}
}
Thanks!
Dorian
… On Mar 6, 2025, at 9:00 PM, Stephen Booth ***@***.***> wrote:
I'm definitely curious to see what you've made!
—
Reply to this email directly, view it on GitHub <#245 (reply in thread)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABLTNEBKD3YVSKDW5HF6SV32TD4S5AVCNFSM6AAAAAASMEFLF2VHI2DSMVQWIX3LMV43URDJONRXK43TNFXW4Q3PNVWWK3TUHMYTENBSGA3TSNQ>.
You are receiving this because you authored the thread.
|
Beta Was this translation helpful? Give feedback.



Uh oh!
There was an error while loading. Please reload this page.
-
Is there an EQ method or a plan to implement?
Thanks,
Dorian
Beta Was this translation helpful? Give feedback.
All reactions