Post

Replies

Boosts

Views

Activity

Playlist IDs from MusicKit not working with Apple Music API
I am building an app for MacOS and I am trying to implement the code to add songs to a library playlist (which is added below). The issue I am having is that if I use Music Kit to load a users library playlists, the ID for the playlist (which is just a string of numbers) does not work with the Add tracks to a Library Playlist endpoint of Apple Music API. If I retrieve the playlists from the Apple Music API and use that playlist ID (which is different than the id I get from MusicKit) my code works fine and adds the song to the playlist. The problem is that when getting a users library playlists from Apple Music API is that it does not give me all of the library playlists that I get when using Music Kit and it also does not give me Artwork for playlists that have the collage of album covers, so I would prefer to use Music Kit to get the playlists. I have also tested trying to retrieve a single playlist using the Apple Music API with the playlist Id from Music Kit and it does not work. I get the error that the resource cannot be found. Since this is a macOs app I cannot use MusicKit to add songs to library playlists. Does anyone know a way to resolve this? Or a possible workaround? Ideally I want to use MusicKit to get the library playlists and have some way to use the playlist Id and add songs to that playlist. Below is my code for adding a song to a playlist using the Apple Music API, which works correctly only if I originally get the library playlist's id value from a playlist retrieved from the Apple Music API. Also, does anyone know why the playlist Id's are not universal and are different when using Music Kit and Apple Music API? For songs and tracks it does not seem to matter if I use music kit or Apple Music API, the Id's are in the correct format for Apple Music API to use and work with my code. Thanks everyone for any and all help! func addToPlaylist(songs: [Track], playlist: Playlist, alert: Binding<AlertItem?>) async { let tracks = AppleMusicPlaylistPostRequestBody(data: songs.compactMap { AppleMusicPlaylistPostRequestItem(id: $0.id.rawValue, type: "songs") // or "library-songs" }) let playlistID = playlist.id // Build the request URL for adding a song to a playlist guard let url = URL(string: "https://api.music.apple.com/v1/me/library/playlists/\(playlistID)/tracks") else { alert.wrappedValue = AlertItem(title: "Error", message: "Invalid URL for the playlist.") return } // Authorization Header guard let musicUserToken = try? await MusicUserTokenProvider().getUserMusicToken() else { alert.wrappedValue = AlertItem(title: "Error", message: "Unable to retrieve Music User Token.") return } do { var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("Bearer \(musicUserToken)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") let encoder = JSONEncoder() let data = try encoder.encode(tracks) request.httpBody = data let musicRequest = MusicDataRequest(urlRequest: request) let musicRequestResponse = try await musicRequest.response() // Check if the request was successful (status 201) if musicRequestResponse.urlResponse.statusCode == 201 { alert.wrappedValue = AlertItem(title: "Success", message: "Song successfully added to the playlist.") } else { print("Status Code: \(musicRequestResponse.urlResponse.statusCode)") print("Response Data: \(String(data: musicRequestResponse.data, encoding: .utf8) ?? "No Data")") // Attempt to decode the error response into the AppleMusicErrorResponse model if let appleMusicError = try? JSONDecoder().decode(AppleMusicErrorResponse.self, from: musicRequestResponse.data) { let errorMessage = appleMusicError.errors.first?.detail ?? "Unknown error occurred." alert.wrappedValue = AlertItem(title: "Error", message: errorMessage) } else { alert.wrappedValue = AlertItem(title: "Error", message: "Failed to add song to the playlist.") } } } catch { alert.wrappedValue = AlertItem(title: "Error", message: "Network error: \(error.localizedDescription)") } }
0
0
191
3w
Help with showing a spectrogram of an audio file
Hello everyone! I am trying to create a spectrogram like in the attached image for a macOs App. I am using Cocoa/AppKit but also have some SwiftUI views, so I can use either. I have found the sample app Visualizing Sound as an Audio Spectogram that apple provides but I do not want a real-time spectrogram. I want a spectrogram of the whole audio file. I have been trying to convert the sample app to what I need but I have been unsuccessful so far. Here is how I changed the code in the delegate public func captureBuffer() { let asset = AVAsset(url: audioFileUrl) let reader = try! AVAssetReader(asset: asset) let track = asset.tracks(withMediaType: AVMediaType.audio)[0] let settings = [ AVFormatIDKey : kAudioFormatLinearPCM ] let readerOutput = AVAssetReaderTrackOutput(track: track, outputSettings: settings) reader.add(readerOutput) reader.startReading() while let buffer = readerOutput.copyNextSampleBuffer() {   var audioBufferList = AudioBufferList(mNumberBuffers: 1, mBuffers: AudioBuffer(mNumberChannels: 0, mDataByteSize: 0, mData: nil))   var blockBuffer: CMBlockBuffer?   CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer( buffer, bufferListSizeNeededOut: nil, bufferListOut: &audioBufferList, bufferListSize:  MemoryLayout<AudioBufferList>.size, blockBufferAllocator: nil, blockBufferMemoryAllocator: nil, flags: kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, blockBufferOut: &blockBuffer   ); let buffers = UnsafeBufferPointer<AudioBuffer>(start: &audioBufferList.mBuffers, count: Int(audioBufferList.mNumberBuffers)) for buffer in buffers { let samplesCount = Int(buffer.mDataByteSize) / MemoryLayout<Int16>.size let samplesPointer = audioBufferList.mBuffers.mData!.bindMemory(to: Int16.self, capacity: samplesCount) let samples = UnsafeMutableBufferPointer<Int16>(start: samplesPointer, count: samplesCount) } guard let data = audioBufferList.mBuffers.mData else { return } /// The _Nyquist frequency_ is the highest frequency that a sampled system can properly /// reproduce and is half the sampling rate of such a system. Although  this app doesn't use /// `nyquistFrequency` you may find this code useful to add an overlay to the user interface. if nyquistFrequency == nil { let duration = Float(CMSampleBufferGetDuration(buffer).value) let timescale = Float(CMSampleBufferGetDuration(buffer).timescale) let numsamples = Float(CMSampleBufferGetNumSamples(buffer)) nyquistFrequency = 0.5 / (duration / timescale / numsamples) } if self.rawAudioData.count < AudioSpectrogram.sampleCount * 2 { let actualSampleCount = CMSampleBufferGetNumSamples(buffer) let ptr = data.bindMemory(to: Int16.self, capacity: actualSampleCount) let buf = UnsafeBufferPointer(start: ptr, count: actualSampleCount) rawAudioData.append(contentsOf: Array(buf)) } while self.rawAudioData.count >= AudioSpectrogram.sampleCount { let dataToProcess = Array(self.rawAudioData[0 ..< AudioSpectrogram.sampleCount]) self.rawAudioData.removeFirst(AudioSpectrogram.hopCount) self.processData(values: dataToProcess) } createAudioSpectrogram() } } } I am sure there are different or better ways to go about this, but the only examples I can find are on iOS and use UIKit, but I am building for MacOs. Does anyone know how display a spectrogram for an audio file without having to play the audio file? I dont mind using sox or ffmpeg if that is easier. Greatly appreciated!
2
0
1.5k
Sep ’22
This Mac can’t connect to Apple Media Services because of a problem with "my Apple ID email address"
I am using Xcode 13.2 on MacOS 12.2. I have been building and running my app in Xcode for months with no issues. I stopped working on it for a couple of days while out of town. The next time I ran my app, I get this pop-up warning saying "This Mac can’t connect to Apple Media Services because of a problem with "my Apple ID email address" when the app uses MusicKit to search for an album. When I push "Apple ID Preferences...", it takes me there and another pop-up asks me to "sign in to Media Services using my Apple ID", even though I am already signed in. Once I sign in, my app continues to run. If I hit the "Later" button, the pop-up goes away and my app continues to run just fine. My computer was off when I was out of town, so no one did anything to it and no auto-updates were performed or anything. I tried signing out of iCloud, Apple Music... etc, restarting and signing back into everything as suggested by Apple customer service but that did not help. They say everything is fine with my Apple ID so it is an issue with Xcode. This happens every time I run my app in Xcode. Does anyone have any idea what is going on? Please help. Much appreciated!
3
0
1.5k
Feb ’22
SwiftUI Mac OS toolbar at top of sheet window?
Hi, From a navigation view, I open a sheet view (instead of having a 3-column view). While I can make a toolbar, it only shows at the bottom of the window. Does anyone know how to get the toolbar at the top of the window, if it is even possible? Or is the toolbar only allowed at the bottom of the window in a sheet view? Thanks!
3
0
1.3k
Jan ’22
How to get the output of a for-in loop into a single DataFrame instead of many Dataframes
I am trying to get the data for each track in a MusicItemCollection into a DataFrame. The only way I know to go about this is to use a for-in loop, but this creates a DataFrame for each track instead of a DataFrame for all of the tracks. My code is: let albumTracks = album?.tracks for tracks in albumTracks {      let dataFrame: DataFrame = [          "track": [tracks.trackNumber],          "title": [tracks.title],          "artist": [tracks.artistName],          "release date": [tracks.releaseDate?.formatted(date: .long, time: .omitted) ?? "not available"],          "duration": [tracks.duration],          "isrc": [tracks.isrc]          ]      print(dataFrame) I have also tried  for tracks in self.albumTracks {      var dataFrame = DataFrame.init()      let trackNumColumn = Column.init(name: "track", contents: [String(tracks.trackNumber!)])      dataFrame.append(column: trackNumColumn)      let titleColumn = Column.init(name: "title", contents: [tracks.title])      dataFrame.append(column: titleColumn)      let artistColumn = Column.init(name: "artist", contents: [tracks.artistName])      dataFrame.append(column: artistColumn)      let releaseDateColumn = Column.init(name: "release date", contents: [tracks.releaseDate?.formatted(date: .long, time: .omitted)])      dataFrame.append(column: releaseDateColumn)      let idColumn = Column.init(name: "id", contents: [String(tracks.id.rawValue)])      dataFrame.append(column: idColumn)            print(dataFrame) Both of these methods work outside of a loop according to the tech talks video: https://developer.apple.com/videos/play/tech-talks/10100/ , but I cannot figure out how to call the individual tracks outside of a loop Thanks
4
0
1.5k
Jan ’22
Having trouble getting MusicKit Data into my SwiftUI view
Having trouble getting the data from MusicKit Searches back to my SwiftUI view. Normally I would use a @State variable for the decodable structure, but with MusicKit you dont need to decode anything and I can't figure out how to best get the data back to my view. I call the MusicKit function using .onAppear in my SwiftUI view func getAlbum() async throws -> MusicItemCollection<Album> {         var request = MusicCatalogResourceRequest<Album>(matching: \.id, equalTo: "1440881121")         let response = try await request.response()         print(response.debugDescription)         return response.items        printing debugDescription give these results: MusicCatalogResourceResponse<Album>(   items: [     Album(       id: "1440881121",       title: "Colors",       artistName: "Beck",       contentRating: "explicit",       copyright: Fonograf Records/Capitol Records; ℗ 2017 Fonograf Records, under exclusive license to UMG Recordings, Inc.,       genreNames: [         "Alternative",         "Music"       ],       isCompilation: false,       isComplete: true,       isDigitalMaster: true,       isSingle: false,       releaseDate: "2017-10-13",       trackCount: 11,       upc: "00602557176827" Hoe would you access all of the data in the debugDescription and use it in your SwiftUI view? I can only access the standard name and id. Is anyone using Codable to move the data through a data model? The documentation is not making sense to me even though I am usually ok using it. Also, should I be using `func getAlbum() async throws -> MusicCatalogResourceResponse {' instead of using MusicItemCollection? Thanks
8
0
2.3k
Nov ’21
How to set up and use a DataFrame from the TabularData Framework
Hello again, I have no ides where to start with this. I am using SwiftUI for Mac OS and I need to be able to create a Data Frame straight from the JSON returned from an API call. So far I have been using Table since I know what the JSON will contain. Now I need a way to get the JSON data straight to a a table that I can display in Swift UI because the contents will vary. I think I need to use @dynamicMemberLookup struct DataFrame with the init(jsonData: Data, columns: [String]?, types: [String : JSONType], options: JSONReadingOptions) throws init method, but I am not sure how to set it up and where to set it up. My app is a view-data-model structure: A SwiftUI view that calls the functions to search the APIs. A class that contains all of the API search functions. The returned JSON data is parsed here for the dataModel A dataModel with structures for the json data. The data from the dataModel gets returned to the SwiftUI as a @State variable data = [dataModel]() I would assume the DataFrame would go in the SwiftUI view but I am not sure how to get the JSON data to the Dataframe since I am not going to decode it to a dataModel. Any help Appreciated! Thanks!
0
0
911
Nov ’21
Cannot get [genreNames] into SwiftUI Table
This is a followup to SwiftUI Table does not work when trying to use Int values After getting a result from looking up an album or song in AppleMusic API, I parse the returned json using SwiftyJSON as shown here:  URLSession.shared.dataTask(with: musicRequest) { (data, response, error) in             guard error == nil else { return }             if let json = try? JSON(data: data!) {                 let result = (json["data"]).array!                 for songDetail in result {                     let attributes = songDetail["attributes"]                     let song = SongDetails(albumName: attributes["albumName"].string!,                                artistName: attributes["artistName"].string!,                                artworkURL: attributes["artwork"]["url"].string!,                                composerName: attributes["composerName"].string ?? "-",                                discNumber: attributes["discNumber"].int!,                                durationInMillis: attributes["durationInMillis"].int!,                                genreNames: attributes["genreNames"].arrayValue,                                isrc: attributes["isrc"].string!,                                name: attributes["name"].string!,                                id: attributes["playParams"]["id"].string!,                                releaseDate: attributes["releaseDate"].string!,                                trackNumber: attributes["trackNumber"].int!)                     songDetails.append(song)                     print(songDetails)                 } Notice how [genreNames] needs ".arrayValue" instead of ".string!". I Think the reason is because "genreNames" are listed in an Array of Strings. Here is the data model I use for the parsed results: struct SongDetails {     var albumName: String     var artistName: String     var artworkURL: String     var composerName: String     var discNumber: Int     var durationInMillis: Int     var genreNames: Array<Any>     var isrc: String     var name: String     var id: String     var releaseDate: String     var trackNumber: Int     init(albumName: String, artistName: String, artworkURL: String, composerName: String, discNumber: Int, durationInMillis: Int, genreNames: Array<Any>, isrc: String, name: String, id: String, releaseDate: String, trackNumber: Int) {         self.albumName = albumName         self.artistName = artistName         self.artworkURL = artworkURL         self.composerName = composerName         self.discNumber = discNumber         self.durationInMillis = durationInMillis         self.genreNames = genreNames         self.isrc = isrc         self.name = name         self.id = id         self.releaseDate = releaseDate         self.trackNumber = trackNumber      } } Here I have genreNames as "Array". This set up works and I can call and print jus the genreNames, but everything I have read online says that "[String]" should be used instead of "Array", but if I do that, I get an error in the previous code when using [genreNames].string!. The way I currently have it set up, genreNames show up in the console as genreNames: [Pop, Music] , so I think it is correct. The issue arises when I try to use genreNames in a SwiftUI view. In my SwiftUI view I have a Table where these results go.             HStack {                 WebImage(url: URL(string: songDetail.artworkURL.replacingOccurrences(of: "{w}", with: "5000").replacingOccurrences(of: "{h}", with: "5000")))                     .resizable()                     .indicator(.activity)                     .frame(width: 400, height: 400)                     .cornerRadius(5)                     .shadow(radius: 2)                                  VStack (alignment: .leading) {                     Text("song id: \(songDetail.id)")                         .foregroundColor(.secondary)                         .padding(2)                     Text("title: \(songDetail.name)")                         .foregroundColor(.secondary)                         .padding(2)                     Text("artist: \(songDetail.artistName)")                         .foregroundColor(.secondary)                         .padding(2)                     Text("composer: \(songDetail.composerName)")                         .foregroundColor(.secondary)                         .padding(2)                     Text("disc number \(songDetail.discNumberString)")                         .foregroundColor(.secondary)                         .padding(2)                     Text("track number: \(songDetail.trackNumberString)")                         .foregroundColor(.secondary)                         .padding(2)                     Text("duration in ms: \(songDetail.durationInMillisString)")                         .foregroundColor(.secondary)                         .padding(2)                     Text("release date: \(songDetail.releaseDate)")                         .foregroundColor(.secondary)                         .padding(2)                     Text("isrc: \(songDetail.isrc)")                         .foregroundColor(.secondary)                         .padding(2)                 }                 .font(.system(size: 14))                 }             }         }.padding(.leading, 20.0)         .frame(minWidth:800, idealWidth: 1000, maxWidth: .infinity, minHeight:600, idealHeight: 800, maxHeight: .infinity)         .onAppear {             SKCloudServiceController.requestAuthorization { (status) in                 if status == .authorized {                     self.searchResults = AppleMusicAPI().getSongDetails(self.songId)                 }             }         }     } } extension SongDetails {     var discNumberString: String {         String(discNumber)     }     var trackNumberString: String {         String(trackNumber)     }     var durationInMillisString: String {         String(durationInMillis)     } } The extension at the bottom is how the issue of the original post was solved. It seems that the new SwiftUI Table can only accept String values. To get the genreNames into this table I tried making a similar extension where I used join() to try and make a string of the genreResults, but that did not work either. I can't remember the exact code, but it could have been wrong. So how would this be accomplished? Anyone know? This is the last thing that I need to get into my Table. Thanks! I appreciate the help!
1
0
872
Nov ’21
Not getting results when searching MusicKit using MusicDataRequest
I am trying to use MusicDataRequest to search for albums, artists, and tracks using a search term like the album's names. If I use an albums Id, for example, I get results. The following code gets me results. First is the data model, the I have a class with code for authorization and then the function that searches using Music Kit struct MyAlbumResponse: Decodable {     let data: [Album] } class music {     @State var isAuthorizedForMusicKit = false     var searchTerm = ""     func requestMusicAuthorization() {         Task.detached {             let authorizationStatus = await MusicAuthorization.request()             if authorizationStatus == .authorized {                 self.isAuthorizedForMusicKit = true             } else {                 }             }         } func album() async throws {         let auth = requestMusicAuthorization()         let url = URL(string: "https://api.music.apple.com/v1/catalog/US/albums/1440881121")!         let dataRequest = MusicDataRequest(urlRequest: URLRequest(url: url))         let dataResponse = try await dataRequest.response()         let decoder = JSONDecoder()         let albumResponse = try decoder.decode(MyAlbumResponse.self, from: dataResponse.data)         print(albumResponse.data)     } The above code returns the results I expect. But If I change the function to use the search endpoint, I do not get any results. I dont get any errors either. The data model is also different. struct AlbumSearchResponse: Decodable {     var albums: Albums }      struct Albums: Codable {     var data: [AlbumsDatum]     var href, next: String } struct AlbumsDatum: Codable {     var attributes: Attributes     var href, id, type: String } struct Attributes: Codable {     var artistName: String     var artwork: Artwork     var copyright: String     var genreNames: [String]     var isComplete, isMasteredForItunes, isSingle: Bool     var name: String     var playParams: PlayParams     var releaseDate: String     var trackCount: Int     var url: String     var editorialNotes: EditorialNotes? } class music {     @State var isAuthorizedForMusicKit = false     var searchTerm = ""          func requestMusicAuthorization() {         Task.detached {             let authorizationStatus = await MusicAuthorization.request()             if authorizationStatus == .authorized {                 self.isAuthorizedForMusicKit = true             } else {                                                       }             }         } func musicSearch() async throws {         var auth = requestMusicAuthorization()         let url = URL(string: "https://api.music.apple.com/v1/catalog/us/search?term=colors&limit=10&types=albums")!         let dataRequest = MusicDataRequest(urlRequest: URLRequest(url: url))         let dataResponse = try await dataRequest.response()         let decoder = JSONDecoder()         let albumSearchResponse = try decoder.decode(AlbumSearchResponse.self, from: dataResponse.data)         print(albumSearchResponse.albums)         } I have gone through all of the documentation and example applications but can't find an answer to this. I dont understand why the search endpoint would be not usable, if that is the case. The workaround I have been using is to use MusicCatalogSearchRequest instead, but this does not return enough attributes and I cannot figure out how to have it return more attributes such as totalTracks, copyright, etc. I would be fine using the method if there is a way to add attributes to be included with the results. Here is the code I am using that works. It is also under the same class as the other functions. func Search() async throws {         let auth = requestMusicAuthorization()         let searchTerm = "colors"         Task {                 do {                     // Issue a catalog search request for albums matching the search term.                     var searchRequest = MusicCatalogSearchRequest(term: searchTerm, types: [Album.self])                     searchRequest.limit = 20                     let searchResponse = try await searchRequest.response()                     print(searchResponse)                 }             }         } any and all help is much appreciated. Thanks
2
0
1.3k
Nov ’21
SwiftUI Table does not work when trying to use Int values
Hello Everyone, I am trying to get a SwiftUI Table to display content from my data model. It works as expected if I only use String values in the table, but if I try and use an Int value my app will no longer compile. It just sits there and I have to stop it manually. The only error I receive says "CompileSwiftSources Failed with a nonzero exit code" My code of the data model: import Foundation struct AlbumTracks: Identifiable {     var artistName: String     var artworkURL: String     var composerName: String     var discNumber: Int     var durationInMillis: Int     var isrc: String     var name: String     var id: String     var releaseDate: String     var trackNumber: Int          init(artistName: String, artworkURL: String, composerName: String, discNumber: Int!, durationInMillis: Int!, isrc: String, name: String, id: String, releaseDate: String, trackNumber: Int!) {         self.artistName = artistName         self.artworkURL = artworkURL         self.composerName = composerName         self.discNumber = discNumber         self.durationInMillis = durationInMillis         self.isrc = isrc         self.name = name         self.id = id         self.releaseDate = releaseDate         self.trackNumber = trackNumber     } } Here is the code for my view: struct AlbumDetailView: View {     @Environment(\.presentationMode) var presentationMode     @Binding var albumId: String     @State var searchResults = [AlbumDetails]()     @State var albumTracks = [AlbumTracks]()         var body: some View {                  VStack (alignment: .leading) {             Button(action: {                 presentationMode.wrappedValue.dismiss()             }, label: {                 Image(systemName: "arrow.left")             }).padding(.top).foregroundColor(.red)                  ForEach(searchResults, id:\.id) { albumDetail in             HStack {                 WebImage(url: URL(string: albumDetail.artworkURL.replacingOccurrences(of: "{w}", with: "5000").replacingOccurrences(of: "{h}", with: "5000")))                     .resizable()                     .padding(.top, 10.0)                     .frame(width: 400, height: 400)                     .cornerRadius(5)                     .shadow(radius: 2)                                  VStack (alignment: .leading) {                     Text("song id: \(albumDetail.id)")                         .foregroundColor(.secondary)                         .padding(2)                     Text("title: \(albumDetail.name)")                         .foregroundColor(.secondary)                         .padding(2)                     Text("artist: \(albumDetail.artistName)")                         .foregroundColor(.secondary)                         .padding(2)                     Text("composer: \(albumDetail.copyright)")                         .foregroundColor(.secondary)                         .padding(2)                                          Text("track number: \(albumDetail.trackCount)")                         .foregroundColor(.secondary)                         .padding(2)                                          Text("release date: \(albumDetail.releaseDate)")                         .foregroundColor(.secondary)                         .padding(2)                     Text("isrc: \(albumDetail.upc)")                         .foregroundColor(.secondary)                         .padding(2)                 }                 .font(.system(size: 14))                              }                          Text("\(albumDetail.editorialNotesStandard)")                 .font(.system(size: 14))                 .foregroundColor(.secondary)                 .padding(2)                                      Table(albumTracks) {                 TableColumn("disc", Int: \.discNumber)                 TableColumn("track", value: \.releaseDate)                 TableColumn("title", value: \.name)                 TableColumn("artist", value: \.artistName)                 TableColumn("duration ms", value: \.releaseDate)                 TableColumn("release date", value: \.releaseDate)                 TableColumn("isrc", value: \.isrc)                 TableColumn("track id", value: \.id)                                  }                                      }                      }         .padding(.leading, 20.0)         .frame(minWidth:800, idealWidth: 1000, maxWidth: .infinity, minHeight:600, idealHeight: 800, maxHeight: .infinity)         .onAppear {             SKCloudServiceController.requestAuthorization { (status) in                 if status == .authorized {                     self.searchResults = AppleMusicAPI().getAlbumDetails(self.albumId)                     self.albumTracks = AppleMusicAPI().getAlbumTracks(self.albumId)                                      }             }         }     } } Any Help is much appreciated! Thanks!
4
0
3.0k
Oct ’21