// // ContentViewModel.swift // MSRecognition // // Created by Prismetric tech on 22/02/22. // import UIKit import ShazamKit class ContentViewModel: NSObject, ObservableObject { @Published private(set) var songMatch: SongMatch? = nil private let session = SHSession() private let audioFileURL = Bundle.main.url(forResource: "songname", withExtension: "mp3") @Published private(set) var isMatching = false override init() { super.init() session.delegate = self } private func buffer(audioFile: AVAudioFile, outputFormat: AVAudioFormat) -> AVAudioPCMBuffer? { let frameCount = AVAudioFrameCount((1024 * 64) / (audioFile.processingFormat.streamDescription.pointee.mBytesPerFrame)) let outputFrameCapacity = AVAudioFrameCount(12 * audioFile.fileFormat.sampleRate) guard let inputBuffer = AVAudioPCMBuffer(pcmFormat: audioFile.processingFormat, frameCapacity: frameCount), let outputBuffer = AVAudioPCMBuffer(pcmFormat: outputFormat, frameCapacity: outputFrameCapacity), let converter = AVAudioConverter(from: audioFile.processingFormat, to: outputFormat) else { return nil } while true { let status = converter.convert(to: outputBuffer, error: nil) { inNumPackets, outStatus in do { try audioFile.read(into: inputBuffer) outStatus.pointee = .haveData return inputBuffer } catch { if audioFile.framePosition >= audioFile.length { outStatus.pointee = .endOfStream return nil } else { outStatus.pointee = .noDataNow return nil } } } switch status { case .endOfStream, .error: return nil default: return outputBuffer } } } private func signature() -> SHSignature? { // Create the buffer from the audio file. guard let audioFileURL = audioFileURL, let audioFile = try? AVAudioFile(forReading: audioFileURL), let audioFormat = AVAudioFormat(standardFormatWithSampleRate: 44100, channels: 1), let buffer = buffer(audioFile: audioFile, outputFormat: audioFormat) else { return nil } // Initialise the generator and append the buffer to it. let signatureGenerator = SHSignatureGenerator() try? signatureGenerator.append(buffer, at: nil) // Return the generated signature. return signatureGenerator.signature() } func startMatching() { // Verify if the signature is not nil, // and if the session not already searching for a match. guard let signature = signature(), isMatching == false else { return } isMatching = true // Search for a match. session.match(signature) } } extension ContentViewModel: SHSessionDelegate { func session(_ session: SHSession, didFind match: SHMatch) { // Get the matched media item if it exists. guard let matchedMediaItem = match.mediaItems.first else { return } // Set isMatching to false, // and set the songMatch property with the matched media's metadata. DispatchQueue.main.async { [weak self] in self?.isMatching = false print(matchedMediaItem.title ?? "title") print(matchedMediaItem.artist ?? "artist") print(matchedMediaItem.artworkURL?.absoluteString ?? "artworkurl") self?.songMatch = SongMatch(appleMusicURL: matchedMediaItem.appleMusicURL, artist: matchedMediaItem.artist, artworkURL: matchedMediaItem.artworkURL, title: matchedMediaItem.title) } } } struct SongMatch { let appleMusicURL: URL? let artist: String? let artworkURL: URL? let title: String? }