Skip to content

Commit

Permalink
Made it possible to pause.
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyome22 committed Aug 12, 2024
1 parent 3d708dc commit 05393a7
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 27 deletions.
3 changes: 2 additions & 1 deletion Examples/Example_for_iOS/MusicView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ struct MusicView: View {
.navigationBarTitleDisplayMode(.inline)
.onAppear {
do {
try audioAnalyzer.play(url: musicItem.url)
try audioAnalyzer.prepare(url: musicItem.url)
try audioAnalyzer.play()
} catch {
print(error.localizedDescription)
}
Expand Down
11 changes: 4 additions & 7 deletions Examples/Example_for_macOS/MusicListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ struct MusicListView: View {

var body: some View {
VStack {
Text("Title: \(model.playingMusicItem?.title ?? "unknown")")
Text("Title: \(model.selectedMusicItem?.title ?? "-")")
AmplitudeSpectrumView(
shapeType: .straight,
magnitudes: model.audioAnalyzer.magnitudes,
Expand All @@ -19,21 +19,18 @@ struct MusicListView: View {
HStack {
Text(musicItem.title)
Spacer()
if musicItem == model.playingMusicItem {
if musicItem == model.selectedMusicItem, model.isPlaying {
Button {
model.playingMusicItem = nil
model.stop()
model.pause()
} label: {
Image(systemName: "stop.fill")
Image(systemName: "pause.fill")
}
} else {
Button {
model.playingMusicItem = musicItem
model.play(musicItem: musicItem)
} label: {
Image(systemName: "play.fill")
}
.disabled(model.playingMusicItem != nil)
}
}
}
Expand Down
36 changes: 23 additions & 13 deletions Examples/Example_for_macOS/MusicListViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import iTunesLibrary
import Observation
import SwiftUI

@Observable
@Observable
final class MusicListViewModel {
var musicItems = [MusicItem]()
var playingMusicItem: MusicItem?
var selectedMusicItem: MusicItem?
var isPlaying = false
let audioAnalyzer = AudioAnalyzer(fftSize: 2048, windowType: .hammingWindow)

func loadMusicItems() {
Expand All @@ -27,20 +28,29 @@ final class MusicListViewModel {
}

func play(musicItem: MusicItem) {
let url = musicItem.url
if url.startAccessingSecurityScopedResource() {
defer {
url.stopAccessingSecurityScopedResource()
}
do {
try audioAnalyzer.play(url: url)
} catch {
Swift.print(error.localizedDescription)
do {
if musicItem != selectedMusicItem {
if selectedMusicItem != nil {
audioAnalyzer.stop()
}
let url = musicItem.url
if url.startAccessingSecurityScopedResource() {
defer {
url.stopAccessingSecurityScopedResource()
}
try audioAnalyzer.prepare(url: url)
selectedMusicItem = musicItem
}
}
try audioAnalyzer.play()
isPlaying = true
} catch {
Swift.print(error.localizedDescription)
}
}

func stop() {
audioAnalyzer.stop()
func pause() {
audioAnalyzer.pause()
isPlaying = false
}
}
32 changes: 26 additions & 6 deletions Sources/AudioVisualizerKit/AudioAnalyzer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import Observation

protocol AudioAnalyzerProtocol: AnyObject {
init(fftSize: Int, windowType: WindowType)
func play(url: URL) throws
func prepare(url: URL) throws
func play() throws
func stop()
}

Expand All @@ -13,6 +14,7 @@ public final class AudioAnalyzer: AudioAnalyzerProtocol {
private let audioEngine = AVAudioEngine()
private let playerNode = AVAudioPlayerNode()
private let fft: FFT
private var isPrepared = false

public var magnitudes: [Magnitude]
public var rms: Float = .zero
Expand All @@ -23,7 +25,8 @@ public final class AudioAnalyzer: AudioAnalyzerProtocol {
magnitudes = .init(repeating: .zero, count: fftSize / 2)
}

public func play(url: URL) throws {
public func prepare(url: URL) throws {
stop()
let audioFile = try AVAudioFile(forReading: url)
let sampleRate = Float(audioFile.processingFormat.sampleRate)
audioEngine.attach(playerNode)
Expand All @@ -40,24 +43,41 @@ public final class AudioAnalyzer: AudioAnalyzerProtocol {
self?.calculate(sampleRate: sampleRate, buffer: buffer)
}
playerNode.scheduleFile(audioFile, at: nil)
try audioEngine.start()
playerNode.play()
isPrepared = true
}

public func play() throws {
guard isPrepared else { return }
if !audioEngine.isRunning {
try audioEngine.start()
}
if !playerNode.isPlaying {
playerNode.play()
}
}

public func pause() {
if playerNode.isPlaying {
playerNode.pause()
}
}

public func stop() {
guard isPrepared else { return }
if playerNode.isPlaying {
playerNode.stop()
}
playerNode.removeTap(onBus: 0)
playerNode.removeTap(onBus: .zero)
if audioEngine.isRunning {
audioEngine.stop()
}
audioEngine.disconnectNodeOutput(playerNode)
audioEngine.detach(playerNode)
isPrepared = false
}

func calculate(sampleRate: Float, buffer: AVAudioPCMBuffer) {
if let data = buffer.floatChannelData {
if let data = buffer.floatChannelData, playerNode.isPlaying {
magnitudes = fft.compute(sampleRate: sampleRate, audioData: data.pointee)
rms = fft.rms(audioData: data.pointee)
}
Expand Down

0 comments on commit 05393a7

Please sign in to comment.