Я делаю базовое музыкальное приложение для iOS, где нажатие нот вызывает воспроизведение соответствующего звука. Я пытаюсь сохранить несколько звуков в буферах для одновременного воспроизведения с минимальной задержкой. Однако в любой момент я могу включить только один звук.
Сначала я настроил свои звуки, используя несколько объектов AVAudioPlayer, назначив звук каждому проигрывателю. Хотя он воспроизводил несколько звуков одновременно, казалось, что он не способен запускать два звука одновременно (казалось, что он немного задерживает второй звук после запуска первого звука). Более того, если я нажимал ноты с очень высокой скоростью, казалось, что движок не успевал, и более поздние звуки начинались хорошо после того, как я нажимал более поздние ноты.
Я пытаюсь решить эту проблему, и из проведенного мной исследования кажется, что использование AVAudioEngine для воспроизведения звуков было бы лучшим методом, где я могу настроить звуки в массиве буферов, а затем заставить их воспроизводить из этих буферов.
class ViewController: UIViewController
{
// Main Audio Engine and it's corresponding mixer
var audioEngine: AVAudioEngine = AVAudioEngine()
var mainMixer = AVAudioMixerNode()
// One AVAudioPlayerNode per note
var audioFilePlayer: [AVAudioPlayerNode] = Array(repeating: AVAudioPlayerNode(), count: 7)
// Array of filepaths
let noteFilePath: [String] = [
Bundle.main.path(forResource: "note1", ofType: "wav")!,
Bundle.main.path(forResource: "note2", ofType: "wav")!,
Bundle.main.path(forResource: "note3", ofType: "wav")!]
// Array to store the note URLs
var noteFileURL = [URL]()
// One audio file per note
var noteAudioFile = [AVAudioFile]()
// One audio buffer per note
var noteAudioFileBuffer = [AVAudioPCMBuffer]()
override func viewDidLoad()
{
super.viewDidLoad()
do
{
// For each note, read the note URL into an AVAudioFile,
// setup the AVAudioPCMBuffer using data read from the file,
// and read the AVAudioFile into the corresponding buffer
for i in 0...2
{
noteFileURL.append(URL(fileURLWithPath: noteFilePath[i]))
// Read the corresponding url into the audio file
try noteAudioFile.append(AVAudioFile(forReading: noteFileURL[i]))
// Read data from the audio file, and store it in the correct buffer
let noteAudioFormat = noteAudioFile[i].processingFormat
let noteAudioFrameCount = UInt32(noteAudioFile[i].length)
noteAudioFileBuffer.append(AVAudioPCMBuffer(pcmFormat: noteAudioFormat, frameCapacity: noteAudioFrameCount)!)
// Read the audio file into the buffer
try noteAudioFile[i].read(into: noteAudioFileBuffer[i])
}
mainMixer = audioEngine.mainMixerNode
// For each note, attach the corresponding node to the audioEngine, and connect the node to the audioEngine's mixer.
for i in 0...2
{
audioEngine.attach(audioFilePlayer[i])
audioEngine.connect(audioFilePlayer[i], to: mainMixer, fromBus: 0, toBus: i, format: noteAudioFileBuffer[i].format)
}
// Start the audio engine
try audioEngine.start()
// Setup the audio session to play sound in the app, and activate the audio session
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.soloAmbient)
try AVAudioSession.sharedInstance().setMode(AVAudioSession.Mode.default)
try AVAudioSession.sharedInstance().setActive(true)
}
catch let error
{
print(error.localizedDescription)
}
}
func playSound(senderTag: Int)
{
let sound: Int = senderTag - 1
// Set up the corresponding audio player to play its sound.
audioFilePlayer[sound].scheduleBuffer(noteAudioFileBuffer[sound], at: nil, options: .interrupts, completionHandler: nil)
audioFilePlayer[sound].play()
}
Каждый звук должен воспроизводиться, не прерывая другие звуки, прерывая только свой собственный звук при повторном воспроизведении звуков. Однако, несмотря на настройку нескольких буферов и проигрывателей и назначение каждого из них на его собственную шину на микшере audioEngine, воспроизведение одного звука по-прежнему останавливает воспроизведение любых других звуков.
Кроме того, хотя исключение .interrupts не позволяет звукам останавливать другие звуки, эти звуки не будут воспроизводиться до тех пор, пока не завершится воспроизведение звука, воспроизводимого в данный момент. Это означает, что если я играю note1, затем note2, затем note3, note1 будет воспроизводиться, тогда как note2 будет воспроизводиться только после завершения note1, а note3 будет воспроизводиться только после завершения note2.
Изменить: мне удалось заставить audioFilePlayer снова вернуться к началу без использования прерывания со следующим кодом в функции playSound.
if audioFilePlayer[sound].isPlaying == true
{
audioFilePlayer[sound].stop()
}
audioFilePlayer[sound].scheduleBuffer(noteAudioFileBuffer[sound], at: nil, completionHandler: nil)
audioFilePlayer[sound].play()
Это все еще заставляет меня выяснять, как воспроизводить эти звуки одновременно, поскольку воспроизведение другого звука все равно остановит текущий воспроизводимый звук.
Изменить 2: я нашел решение своей проблемы. Мой ответ ниже.