Has recent update affected genData in AudioKit? - midi

*** Update 2 ***
Ok, done some more digging and managed to get things working with MIKMIDI by starting at position 1 rather than position 0; the same fix hasn't worked with AudioKit.
Further, I've created a new, ugly AF, app that replicates behaviour across both frameworks and outputs, among other things, the track data, and there is definitely a difference between them. I include the code below for perusal. You'll need both MIKMIDI and AudioKit available as frameworks, and a soundfont. Both appear to be working identically but the generated data is different. Again, it could be that I'm making a fundamental error for which I apologise if that's the case, but if anyone can point out the issue I'd be grateful. Many thanks.
import SwiftUI
import MIKMIDI
import AudioKit
let MIKsequence = MIKMIDISequence()
let MIKsequencer = MIKMIDISequencer()
var AKoutputSampler = MIDISampler(name: "output")
var AKsequencer = AppleSequencer()
var AKmixer = Mixer()
var AKengine = AudioEngine()
func AKsetup() {
print("AK Setup Start---------")
AKmixer.addInput(AKoutputSampler)
AKengine.output = AKmixer
do {
try AKengine.start()
} catch {
print("error starting the engine: \(error)")
}
print("AK Setup End ----------")
}
func AKinitialise(){
print("AK Initilise Start --------")
AKsequencer = AppleSequencer()
for t in 0..<AKsequencer.tracks.count {
AKsequencer.deleteTrack(trackIndex: t)
}
let AKtrackManager = AKsequencer.newTrack("piano")
for note in 1..<6{
AKtrackManager?.add(noteNumber: MIDINoteNumber(note+60), velocity: 100, position: Duration(beats: Double(note * 16)/16), duration: Duration(beats: 0.25),channel: 1)
}
let length = AKtrackManager?.length
print("Length = \(length)")
let mnd : [MIDINoteData] = (AKtrackManager?.getMIDINoteData())!
for d in mnd {
print("Note \(d.noteNumber), position \(d.position.seconds)")
}
AKsequencer.setLength(Duration(beats: Double(length!)))
AKsequencer.disableLooping()
AKsequencer.setTempo(120)
AKsequencer.addTimeSignatureEvent(timeSignature: TimeSignature(topValue: 4, bottomValue: .four))
AKtrackManager?.setMIDIOutput(AKoutputSampler.midiIn)
let hexValues = AKsequencer.genData()!.map { String(format: "%02X", $0) }
print(hexValues.joined(separator: " "))
AKsequencer.debug()
print("AK Initialise End ---------")
}
func loadSF2(name: String, ext: String, preset: Int, sampler: MIDISampler) {
print("Load SF2 Start")
guard let url = Bundle.main.url(forResource: name, withExtension: ext) else {
print("LoadSF2: Could not get SoundFont URL")
return
}
do {
try sampler.loadMelodicSoundFont(url: url, preset: preset)
} catch {
print("can not load SoundFont \(name) with error: \(error)")
}
print("Load SF2 End")
}
func AKplay() {
AKengine.stop()
loadSF2(name: "Chaos Bank", ext: "sf2", preset: 1, sampler: AKoutputSampler)
do {
try AKengine.start()
} catch {
print("error starting the engine: \(error)")
}
AKsequencer.play()
}
func AKstop(){
AKsequencer.stop()
AKsequencer.rewind()
}
func MIKinitialise(){
print("MIK Initialise Start")
do {
let tempo = 120.0
let signature = MIKMIDITimeSignature(numerator: 4, denominator: 4)
MIKsequence.setOverallTempo(tempo)
MIKsequence.setOverallTimeSignature(signature)
for t in MIKsequence.tracks {
MIKsequence.removeTrack(t)
}
let _ = try MIKsequence.addTrack()
let track = MIKsequence.tracks[0]
let trackSynth = MIKsequencer.builtinSynthesizer(for: track)
if let soundfont = Bundle.main.url(forResource: "Chaos Bank", withExtension: "sf2") {
do {
try trackSynth?.loadSoundfontFromFile(at: soundfont)
} catch {
print("can not load SoundFont with error: \(error)")
}
let instrumentId = MIKMIDISynthesizerInstrument(id: 10, name: "Eric")
try trackSynth!.selectInstrument(instrumentId!, error: ())
print("Available Instruments \(trackSynth!.availableInstruments)")
}
var notes = [MIKMIDINoteEvent]()
for n in 1..<6 {
let note = MIKMIDINoteEvent(timeStamp:Double(n),note:UInt8(60 + n),velocity:100,duration:0.25,channel:1)
notes.append(note)
}
track.addEvents(notes)
let length = track.length
MIKsequence.length = length
MIKsequencer.sequence = MIKsequence
print("Duration in seconds \(MIKsequencer.sequence.durationInSeconds)")
print("Tempo Track \(MIKsequence.tempoTrack.length), \(MIKsequence.tempoTrack.notes.count)")
for t in MIKsequence.tracks {
print("Track Number \(t.trackNumber)")
for notes in t.notes {
print("Note \(notes.note), \(notes.duration), \(notes.timeStamp)")
}
}
let hexValues = MIKsequencer.sequence.dataValue!.map { String(format: "%02X", $0) }
print(hexValues.joined(separator: " "))
} catch let error {
print(error.localizedDescription)
}
print("MIK Initialise End")
}
func startMIKPlayback(){
MIKsequencer.startPlayback()
}
func stopMIKPlayback(){
MIKsequencer.stop()
}
func getDocumentsDirectory() -> URL {
// find all possible documents directories for this user
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
// just send back the first one, which ought to be the only one
print(paths[0])
return paths[0]
}
func saveMIKFile()->String{
let date = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYYMMddHHmmss"
let filename = dateFormatter.string(from: date) + " MIK Song.mid"
try! MIKsequence.write(to: getDocumentsDirectory().appendingPathComponent(filename))
return getDocumentsDirectory().absoluteString
}
func saveAudioKitFile()->String{
let date = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYYMMddHHmmss"
let filename = dateFormatter.string(from: date) + "AK Song.mid"
try! AKsequencer.genData()!.write(to: getDocumentsDirectory().appendingPathComponent(filename))
return getDocumentsDirectory().absoluteString
}
struct ContentView: View {
init(){
AKsetup()
MIKinitialise()
}
var body: some View {
HStack{
VStack {
Text("MIKMIDI Test 01")
Button("Play", action: startMIKPlayback)
Button("Stop", action: stopMIKPlayback)
Button("Save") {
let _ = print(saveMIKFile())
}
}
.padding()
VStack {
Text("AudioKit Test 01")
let _ = AKinitialise()
Button("Play", action: AKplay)
Button("Stop", action: AKstop)
Button("Save") {
let _ = print(saveAudioKitFile())
}
}
.padding()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
*** End Update 2 ***
*** Update ***
Still having the same problem, and now I've tried with both AudioKit and MIKMIDI. I've run the generated file through a midi analyzer online and it says "Undefined Variable: Last". I've reached out to both MIKMIDI and Midi-Analyzer authors to see if they can assist but if anyone can throw light on this issue, I'd be grateful.
*** End Update ***
I'm working on an app that saves a midi sequence to file, using AudioKit and genData(). However, it seems that a recent update - either OS or Audiokit - has affected the way things save.
The startnote now seems to be offset on tracks by a varying amount, and the rest of the track then follows that offset. Oftentimes the end notes of the track may be missing. This problem was not occurring until recently.
Showing output of the sequence shows the data in the correct positions but it's coming out like this (the notes should be starting at position 0 in the track):
Pattern Offset shown in Garageband
I've also had difficulty in importing the same midi file into other packages; again, this wasn't a problem until recently.
I'm happy to be told I'm doing something amiss but, as I've said, it seems to have been working up until recently.
Any help would be really appreciated.
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
func saveFile()->String{
try! sequencer.genData()!.write(to: getDocumentsDirectory().appendingPathComponent("QuickTestmidi.mid"))
return getDocumentsDirectory().absoluteString
}
func setSequence_01(){
var trackLength = 0.0
sequencer.tracks[0].setMIDIOutput(outputSampler[0].midiIn)
for track in sequencer.tracks {
track.clear()
}
for i in 0..<16 {
sequencer.tracks[0].add(noteNumber: MIDINoteNumber(96 + i), velocity: MIDIVelocity(100), position: Duration(beats: Double(2 * i)), duration: Duration(beats: 1),channel: 1)
trackLength = Double(sequencer.tracks[0].length)
sequencer.setLength(Duration(beats:trackLength))
sequencer.enableLooping()
sequencer.setTempo(120)
sequencer.addTimeSignatureEvent(timeSignature: TimeSignature(topValue: 4, bottomValue: .four))
}
}

Related

How to Read Data from Text File iOS 15 [duplicate]

This question already has answers here:
UIDocumentPickerViewController is not working when testing with my iPad but fine with simulators
(2 answers)
Closed 12 months ago.
Update: This code works in the simulator, but not on my device. Obviously, I'm needing it to work on both.
I've followed the tutorials, yet I cannot seem to get this feature to work. When the user selects the barButtonItem, DocumentPicker opens allowing the user to select a .txt file. I then take the URL to the selected file and attempt to return a string from it; however, I'm getting the following error: "The file “Test.txt” couldn’t be opened because you don’t have permission to view it." What am I missing? Did I fail to ask for permission somewhere? I've tried cleaning the build folder - didn't work.
#IBAction func importFileBtnTapped(_ sender: Any) {
selectFiles()
}
func selectFiles() {
let types = UTType.types(tag: "txt",
tagClass: UTTagClass.filenameExtension,
conformingTo: nil)
let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: types)
documentPickerController.delegate = self
self.present(documentPickerController, animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let myURL = urls.first else {
let alert = SCLAlertView()
alert.showError("ERROR", subTitle: "Unable to retrieve document.")
return
}
let text = createStringFromSelectedFile(fileURL: myURL)
if text == "error" {
print("ERROR creating a string from the selected file.")
return
}
let separatedStrings = decipherString(text: text)
if separatedStrings.first == "error" {
print("ERROR deciphering the string in ClaimInfoViewController")
return
}
for string in separatedStrings {
print("\(string)")
}
print("import result: \(myURL)")
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
dismiss(animated: true, completion: nil)
}
func createStringFromSelectedFile(fileURL: URL) -> String {
var text = String()
do {
text = try String(contentsOf: fileURL)
}
catch {
print("ERROR in the createStringFromSelectedFile function in ClaimInfoViewController")
print("The error: \(error.localizedDescription)")
let alert = SCLAlertView()
alert.showError("ERROR", subTitle: "Unable to read the file. Please try again.")
return "error"
}
return text
}
func decipherString(text: String) -> [String]{
let newText = text
let startIndexes = ["<Claim#",
"<File#",
"<DateOfLoss:"
]
var claimNumber = String()
var fileNumber = String()
var dateOfLoss = String()
for indexValue in startIndexes {
guard let index = newText.firstIndex(of: ">") else { return ["error"] }
let newString = String(newText[..<index])
if indexValue == "<Claim#" {
claimNumber = newString
}
else if indexValue == "<File#" {
fileNumber = newString
}
else if indexValue == "<DateOfLoss:" {
dateOfLoss = newString
}
}
let finalText = [claimNumber, fileNumber, dateOfLoss]
return finalText
}
Thanks to matt, who commented above, I was able to find out that it's a security issue. Adding this simple code resolved the issue:
let shouldStopAccessing = pickedFolderURL.startAccessingSecurityScopedResource()
defer {
if shouldStopAccessing {
pickedFolderURL.stopAccessingSecurityScopedResource()
}
}
I added it right before this line of code that can be seen above:
let text = createStringFromSelectedFile(fileURL: myURL)
I got this code from here: StackOverflow Post

Too many open files using NWListener on macOS

I'm trying to create an app that listens for incoming data on a UDP port using NWListener. This works fine until 248 messages are received - at which point the app crashes with the error message nw_listener_inbox_accept_udp socket() failed [24: Too many open files].
This (I think) relates to the file descriptor limit and so I tried resetting the NWListener within a safe count of 100, but the problem still persists. This seems clumsy and I'm not sure it's actually possible within a receive.
How can I truly reset it such that it releases any open files?
Full code below, which is instantiated within a SwiftUI content view using:
.onAppear() {
udpListener.start(port: self.udpPort)
}
Full class:
import Foundation
import Network
class UdpListener: NSObject, ObservableObject {
private var listener: NWListener?
private var port: NWEndpoint.Port?
#Published var incoming: String = ""
#Published var messageCount: Int = 0
func start(port: NWEndpoint.Port) {
self.port = port
do {
let params = NWParameters.udp
params.allowLocalEndpointReuse = true
self.listener = try NWListener(using: params, on: port)
self.listener?.stateUpdateHandler = {(newState) in
switch newState {
case .ready:
print("ready")
default:
break
}
}
self.listener?.newConnectionHandler = {(newConnection) in
newConnection.stateUpdateHandler = {newState in
switch newState {
case .ready:
self.receive(on: newConnection)
default:
break
}
}
newConnection.start(queue: DispatchQueue(label: "new client"))
}
} catch {
print("unable to create listener")
}
self.listener?.start(queue: .main)
}
func receive(on connection: NWConnection) {
connection.receiveMessage { (data, context, isComplete, error) in
if let error = error {
print(error)
return
}
guard let data = data, !data.isEmpty else {
print("unable to receive data")
return
}
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss.SSSS"
DispatchQueue.main.async {
self.messageCount = self.messageCount + 1
self.incoming = formatter.string(from: date) + " " + String(decoding: data, as: UTF8.self) + "\n" + self.incoming
}
if self.messageCount == 100 {
print("Resetting")
self.listener?.stateUpdateHandler = nil
self.listener?.newConnectionHandler = nil
self.listener?.cancel()
self.listener = nil
self.start(port: self.port!)
}
}
}
}

Swift HealthKit HKStatisticsCollectionQuery statisticsUpdateHandler not always called

I have a test project where I get the total number of falls for a user for each day over the course of the week. The initialResultsHandler works perfectly every time, however the statisticsUpdateHandler doesn't always fire off. If you start the app, then go to the health app and insert falls manually, switch back to the test app you should see the total for today update. In reality this works for about the first 3-6 times. After that the statisticsUpdateHandler doesn't get called anymore.
What's also odd is that if you delete data and then go back to the test app, or add data from a time earlier than now, the statisticsUpdateHandler gets called. This leads me to think that it has something to do with the statisticsUpdateHandler end date.
Apples documentation is pretty clear however I’m afraid they might be leaving something out.
If this property is set to nil, the statistics collection query will automatically stop as soon as it has finished calculating the initial results. If this property is not nil, the query behaves similarly to the observer query. It continues to run, monitoring the HealthKit store. If any new, matching samples are saved to the store—or if any of the existing matching samples are deleted from the store—the query executes the update handler on a background queue.
Is there any reason that statisticsUpdateHandler might not be called? I have included a test project below.
struct Falls: Identifiable{
let id = UUID()
let date: Date
let value: Int
var formattedDate: String{
let formatter = DateFormatter()
formatter.setLocalizedDateFormatFromTemplate("MM/dd/yyyy")
return formatter.string(from: date)
}
}
struct ContentView: View {
#StateObject var manager = HealthKitManager()
var body: some View {
NavigationView{
List{
Text("Updates: \(manager.updates)")
ForEach(manager.falls){ falls in
HStack{
Text(falls.value.description)
Text(falls.formattedDate)
}
}
}
.overlay(
ProgressView()
.scaleEffect(1.5)
.opacity(manager.isLoading ? 1 : 0)
)
.navigationTitle("Falls")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
class HealthKitManager: ObservableObject{
let healthStore = HKHealthStore()
let fallType = HKQuantityType.quantityType(forIdentifier: .numberOfTimesFallen)!
#Published var isLoading = false
#Published var falls = [Falls]()
#Published var updates = 0
init() {
let healthKitTypesToRead: Set<HKSampleType> = [fallType]
healthStore.requestAuthorization(toShare: nil, read: healthKitTypesToRead) { (success, error) in
if let error = error{
print("Error: \(error)")
} else if success{
self.startQuery()
}
}
}
func startQuery(){
let now = Date()
let cal = Calendar.current
let sevenDaysAgo = cal.date(byAdding: .day, value: -7, to: now)!
let startDate = cal.startOfDay(for: sevenDaysAgo)
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: now, options: [.strictStartDate, .strictEndDate])
var interval = DateComponents()
interval.day = 1
// start from midnight
let anchorDate = cal.startOfDay(for: now)
let query = HKStatisticsCollectionQuery(
quantityType: fallType,
quantitySamplePredicate: predicate,
options: .cumulativeSum,
anchorDate: anchorDate,
intervalComponents: interval
)
query.initialResultsHandler = { query, collection, error in
guard let collection = collection else {
print("No collection")
DispatchQueue.main.async{
self.isLoading = false
}
return
}
collection.enumerateStatistics(from: startDate, to: Date()){ (result, stop) in
guard let sumQuantity = result.sumQuantity() else {
return
}
let totalFallsForADay = Int(sumQuantity.doubleValue(for: .count()))
let falls = Falls(date: result.startDate, value: totalFallsForADay)
print(falls.value, falls.formattedDate)
DispatchQueue.main.async{
self.falls.insert(falls, at: 0)
}
}
print("initialResultsHandler done")
DispatchQueue.main.async{
self.isLoading = false
}
}
query.statisticsUpdateHandler = { query, statistics, collection, error in
print("In statisticsUpdateHandler...")
guard let collection = collection else {
print("No collection")
DispatchQueue.main.async{
self.isLoading = false
}
return
}
DispatchQueue.main.async{
self.isLoading = true
self.updates += 1
self.falls.removeAll(keepingCapacity: true)
}
collection.enumerateStatistics(from: startDate, to: Date()){ (result, stop) in
guard let sumQuantity = result.sumQuantity() else {
return
}
let totalFallsForADay = Int(sumQuantity.doubleValue(for: .count()))
let falls = Falls(date: result.startDate, value: totalFallsForADay)
print(falls.value, falls.formattedDate)
print("\n\n")
DispatchQueue.main.async{
self.falls.insert(falls, at: 0)
}
}
print("statisticsUpdateHandler done")
DispatchQueue.main.async{
self.isLoading = false
}
}
isLoading = true
healthStore.execute(query)
}
}
I was so focused on the statisticsUpdateHandler and the start and end time that I didn't pay attention to the query itself. It turns out that the predicate was the issue. By giving it an end date, it was never looking for samples outside the the initial predicate end date.
Changing the predicate to this solved the issue:
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: nil, options: [.strictStartDate])

Assigning MyScruct().var = results results in no assignment

Ok.. probably bad title. But here, the problem.
struct DeckView: View {
#State public var results = [ScryfallCard]()
var body: some View {
List(results, id: \.id ) { item in
Mkae a list containing the results.
}.onAppear {
ScryfallData().parseBulkData()
print("Type of results::", type(of: results))
print("results.capacity:", results.capacity)
}
}
}
struct ScryfallData {
func parseBulkData() {
let fm = FileManager.default
let path = Bundle.main.resourcePath
let items = try! fm.contentsOfDirectory(atPath: path!)
var oracleFileName = ""
for fileName in items {
if fileName .hasPrefix("oracle-cards"){
oracleFileName = fileName
}
}
print("if let savedJson = Bundle.main.url")
if let savedJson = Bundle.main.url(forResource: oracleFileName, withExtension: "") {
if let dataOfJson = try? Data(contentsOf: savedJson) {
print("if let dataOfJSON: \(dataOfJson)")
do {
let scryfallDecodeData = try JSONDecoder().decode([ScryfallCard].self, from: dataOfJson)
print("scryfallDecodeData.capacity:", scryfallDecodeData.capacity)
/* error here*/ DeckView().results = scryfallDecodeData
print("DeckView().results: ", DeckView().results)
print("Decoded data:", type(of: scryfallDecodeData))
} catch {
debugPrint("decode failed")
}
}
}
}
}
I keep getting a blank List this in the debugger...
if let dataOfJSON: 73545913 bytes
scryfallDecodeData.capacity: 24391
DeckView().results: []
Decoded data: Array<ScryfallCard>
Type of results:: Array<ScryfallCard>
results.capacity: 0
This means that oiver on the line marked Error Here, I'm asigning the decoded data to the DeckView().results var, but the end result is the data is not getting asigned. Any idea what I'm doing wrong?
You should not be creating a View from view model (ScryfallData), but instead return the decoded data from the parseBulkData function and assign that to results inside the onAppear of your View.
Your models should never know about your UI. Your UI (View in case of SwiftUI) should own the models, not the other way around. This achieves good separation of concerns and also makes your business logic platform and UI agnostic.
struct DeckView: View {
#State public var results = [ScryfallCard]()
var body: some View {
List(results, id: \.id ) { item in
Text(item.text)
}.onAppear {
self.results = ScryfallData().parseBulkData()
}
}
}
struct ScryfallData {
func parseBulkData() -> [ScryfallCard] {
let fm = FileManager.default
let path = Bundle.main.resourcePath
let items = try! fm.contentsOfDirectory(atPath: path!)
var oracleFileName = ""
for fileName in items {
if fileName .hasPrefix("oracle-cards"){
oracleFileName = fileName
}
}
if let savedJson = Bundle.main.url(forResource: oracleFileName, withExtension: "") {
do {
let jsonData = try Data(contentsOf: savedJson)
let scryfallDecodeData = try JSONDecoder().decode([ScryfallCard].self, from: jsonData)
return scryfallDecodeData
} catch {
debugPrint("decode failed")
return []
}
}
return []
}
}

Swift AVFoundation in playground not outputting sound

My Morse code translator will not output the sound as it should. I have tested the speakers and my methods without this function and it works flawlessly, but it is not in context with the rest of the program. The compiler gives me no errors and the playground does not crash, it just doesn't play sound. Volume and ringer is at full.
func speakTheCode(message: String) {
var speaker = AVAudioPlayer()
let longBeep = #fileLiteral(resourceName: "beep_long.mp3")
let shortBeep = #fileLiteral(resourceName: "beep_short.mp3")
let dash = "-"
let dot = "."
for character in message.characters {
if character == dash[dash.startIndex] {
speaker = try! AVAudioPlayer(contentsOf: longBeep)
speaker.prepareToPlay()
print("-")
}
else if character == dot[dot.startIndex] {
speaker = try! AVAudioPlayer(contentsOf: shortBeep)
speaker.prepareToPlay()
print(".")
}
speaker.play()
}
}
I've been messing around with the code for hours now and nothing is working. What (if anything) am I doing wrong?
There seems to some playgrounds issues with playing audio. See this thread:
Playing a sound in a Swift Playground
However, I was able to make some changes to your code and get it to work. Here's my code:
class Morse:NSObject, AVAudioPlayerDelegate {
private var message = ""
private var dotSound:AVAudioPlayer!
private var dashSound:AVAudioPlayer!
private let dash = Character("-")
private let dot = Character(".")
private var index:String.Index!
init(message:String) {
super.init()
do {
if let url = Bundle.main.url(forResource:"beep_short", withExtension:"mp3") {
self.dotSound = try AVAudioPlayer(contentsOf:url)
self.dotSound.delegate = self
self.dotSound.prepareToPlay()
}
} catch {
NSLog("Error loading dot audio!")
}
do {
if let url = Bundle.main.url(forResource:"beep_long", withExtension:"mp3") {
self.dashSound = try AVAudioPlayer(contentsOf:url)
self.dashSound.delegate = self
self.dashSound.prepareToPlay()
}
} catch {
NSLog("Error loading dash audio!")
}
self.message = message
self.index = message.startIndex
}
func playCharacter() {
let character = message.characters[index]
NSLog("Character: \(character)")
if character == dash {
dashSound.play()
} else if character == dot {
dotSound.play()
}
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
NSLog("Finished playing")
if index != message.endIndex {
self.index = message.index(after:index)
playCharacter()
}
}
}
let m = Morse(message:"...---")
m.playCharacter()
PlaygroundPage.current.needsIndefiniteExecution = true
I had to enable indefinite execution to get the code to execute at all. Also, I had some issues with the second audio file loading but I didn't investigate further to see if it was an issue with my test file or something else since it mostly worked.
#Fahim still it is showing error
class Morse:NSObject, AVAudioPlayerDelegate {
private var message = ""
private var dotSound:AVAudioPlayer!
private var dashSound:AVAudioPlayer!
private let dash = Character("-")
private let dot = Character(".")
private var index:String.Index!
init(message:String) {
super.init()
do {
if let url = Bundle.main.url(forResource:"beep_short", withExtension:"mp3") {
self.dotSound = try AVAudioPlayer(contentsOf:url)
self.dotSound.delegate = self
self.dotSound.prepareToPlay()
}
} catch {
NSLog("Error loading dot audio!")
}
do {
if let url = Bundle.main.url(forResource:"beep_long", withExtension:"mp3") {
self.dashSound = try AVAudioPlayer(contentsOf:url)
self.dashSound.delegate = self
self.dashSound.prepareToPlay()
}
} catch {
NSLog("Error loading dash audio!")
}
self.message = message
self.index = message.startIndex
}
func playCharacter() {
let character = message.characters[index]
NSLog("Character: \(character)")
if character == dash {
dashSound.play()
} else if character == dot {
dotSound.play()
}
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
NSLog("Finished playing")
if index != message.endIndex {
self.index = message.index(after:index)
playCharacter()
}
}
}
let m = Morse(message:"...---")
m.playCharacter()
PlaygroundPage.current.needsIndefiniteExecution = true