I'm familiarizing myself with AKMusicTrack functions, specifically functions used to clear note data from a given sequence.
I can see that clearRange() will clear note data between a start and end range, and clearNote() will remove all events in the sequence of that note value.
Does anyone know of a function that will clear a single note? As in something like clearSingleNote(noteNumber MIDINoteNumber, position AKDuration, end AKDuration)?
You can use getMIDINoteData() to get an array of AKMIDINoteData, filter out the notes you don't want, then overwrite the music track using replaceMIDINoteData() with your filtered array:
// remove a C at timestamp 4.0
var trackData = myTrack.getMIDINoteData()
trackData = trackData.filter { $0.noteNumber =! 60 &&
$0.duration =! AKDuration(beats: 4.0) }
myTrack.replaceMIDINoteData(with: trackData)
There are more examples using getMIDINoteData() in the MIDIFileEditAndSync example project.
Right way:
trackData = trackData.filter {
!($0.noteNumber == 60 && $0.duration == AKDuration(beats: 4.0))
}
Related
I have 2 sequencers:
let sequencer1 = AKAppleSequencer(filename: "filename1")
let sequencer2 = AKAppleSequencer(filename: "filename2")
Both have the same bpm value.
When sequencer1 starts playing one midi track (playing it only once) I need that sequencer2 begin playing exactly after first sequencers finished. How can I achieve this ?
Note that sequencer2 looped.
Currently I have this approach but it is not accurate enough:
let callbackInstrument = AKMIDICallbackInstrument(midiInputName: "callbackInstrument", callback: nil)
let callbackTrack = sequencer1.newTrack()!
callbackTrack.setMIDIOutput(callbackInstrument.midiIn)
let beatsCount = sequencer1.length.beats
callbackTrack.add(noteNumber: MIDINoteNumber(beatsCount),
velocity: 1,
position: AKDuration(beats: beatsCount),
duration: AKDuration(beats: 0.1))
callbackInstrument.callback = { status, _, _ in
guard AKMIDIStatusType.from(byte: status) == .noteOn else { return }
DispatchQueue.main.async { self.sequencer2.play() }//not accurate
}
let sampler = AKMIDISampler(midiOutputName: nil)
sequencer1.tracks[0].setMIDIOutput(sampler.midiIn)
Appreciate any thoughts.
Apple's MusicSequence, upon which AKAppleSequencer is built, always flubs the timing for the first 100ms or so after it starts. It is a known issue in closed source code and won't ever be fixed. Here are two possible ways around it.
Use the new AKSequencer. It might be accurate enough to make this work (but no guarantees). Here is an example of using AKSequencer with AKCallbackInstrument: https://stackoverflow.com/a/61545391/2717159
Use a single AKAppleSequencer, but place your 'sequencer2' content after the 'sequencer1' content. You won't be able to loop it automatically, but you can repeatedly re-write it from your callback function (or pre-write it 300 times or something like that). In my experience, there is no problem writing MIDI to AKAppleSequencer while it is playing. The sample app https://github.com/AudioKit/MIDIFileEditAndSync has examples of time shifting MIDI note data, which could be used to accomplish this.
i'm currently working a musician app. In my app notes should be played with a specific duration. I don't get into detail when the notes are played. Basically there is a ui view (a vertical line) which is moving and when this hits my other ui views (rectangle) it should be played a note. Important here: the note should be played until the line is not hitting the rectangle anymore.
The note playing is no problem but I don't find any duration. Also it should be possible to play the same note multiple times with a delay.
So I tried to make this work with AudioKit cause it's seems like the best greatest solution for audio. But it has so much stuff. I took a look into their examples and found this:
let bundlePath = Bundle.main.bundlePath
let soundPath = ("\(bundlePath)/sounds")
let akSampler = AKAppleSampler()
let mixer = AKMixer(akSampler)
try! akSampler.loadSoundFont(soundPath, preset: 0, bank: 0)
mixer.start()
AudioKit.output = mixer
do {
_ = try AudioKit.engine.start()
} catch {
print("AudioKit wouldn't start!")
}
do {
try akSampler.play(noteNumber: myNote.rawValue, velocity: 100, channel: 1)
} catch let e{
print(e)
}
Unfortunately I can't pass any duration and when I call akSampler.stop(noteNumber: myNote.rawValue) it also stops the other notes with the same type.
I tried to find a solution with AVFoundation like so:
engine = AVAudioEngine()
sampler = AVAudioUnitSampler()
engine.attach(sampler)
engine.connect(sampler, to: engine.mainMixerNode, format: nil)
guard let bankURL = Bundle.main.url(forResource: "sounds", withExtension: "SF2") else {
print("could not load sound font")
return
}
... init engine
sampler.startNote(60, withVelocity: 64, onChannel: 0)
But same result. Also the same case that I can't pass any duration.
I also digged into MIDISequencer's but it seems that they generating a sequence which I can play but this does not fit on my problem.
Does someone has a solution here?
The laziest solution would be to just schedule a stop with asyncAfter when you trigger the note, e.g.,
func makeNote(note: MIDINoteNumber, dur: Double) {
sampler.play(noteNumber: note, velocity: 100, channel: 0)
DispatchQueue.main.asyncAfter(deadline: .now() + dur) {
self.sampler.stop(noteNumber: note)
}
}
A better solution would probably use either AKSequencer or AKAppleSequencer. Both allow you to create sequences on the fly by adding individual notes with a specified duration (in musical time, i.e., number of beats). AKSequencer is considerably more accurate, but AKAppleSequencer has more readily available code examples on the web. A little confusingly, the current AKAppleSequencer used to also be called AKSequencer, but their interfaces are sufficiently different that a quick look at the docs for the two classes will tell you which you're looking at.
Your question is asking about how to schedule MIDI events which is precisely what these classes are designed to do. You haven't really given a clear reason why generating a sequence doesn't fit your problem.
I recently upgraded my iOS Project to Swift 3 and iOS 10. Since then I'm running into a weird problem with Realm.
Here is what I try to do: I have a set of Positions, which I want to update with Server Data. So for every existing Position, I want to update it if there exists a newer version. If there is a Server Position, which doesn't exist locally, then I add it.
Here is the Code for that:
let newPositions = serializePositions(jsonResponse)
for newPosition in newPositions {
if let existingPositon = uiRealm.object(ofType: Position.self, forPrimaryKey: newPosition.id as String) {
if (progress.learningVersion < serverLearningVersion) {
try! uiRealm.write {
existingPositon.rank = newPosition.rank
existingPositon.starred = newPosition.starred
}
}
} else {
try! uiRealm.write {
progress.positions.append(newPosition)
}
}
}
If I run this, the something weird happens:
For the firstItem in the loop (the first Position) it works correctly.
But then for the following Positions I get nil for existing Positions, even if it exists.
The Primary Key in the Position Model is a String Field and I use MongoDB Object Ids from the Server.
This is how the Positions are serialized from JSON:
func serializePositions(_ json: JSON) -> List<Position> {
let positions = List<Position>()
let serverPositions = json["positions"].arrayValue
for serverPosition in serverPosition {
let position = Position()
position.id = listItem["id"].stringValue
position.starred = listItem["starred"].boolValue
position.rank = listItem["version"].intValue
positions.append(position)
}
return positions
}
I'm pretty new to Realm and iOS and I hope, that I just make a stupid little mistake here. Thanks in advance for every idea..
Cheers,
Raffi
I discovered the issue. I simply closed a for loop at the wrong position. As the issue was not related to Realm or JSON in the end I don't post the changes. Really it dosnt't help nobody :)
var endLat: Double = 0.0
var endLong: Double = 0.0
func forwardGeocodingStarting(address: String) {
CLGeocoder().geocodeAddressString(address, completionHandler: { (placemarks, error) in
if error != nil {
print(error)
return
}
let placemark = placemarks?[0]
let location = placemark?.location
let coordinate = location?.coordinate
dispatch_async(dispatch_get_main_queue()) {
startLat = (coordinate?.latitude)!
startLong = (coordinate?.longitude)!
print("\(startLat) is lat and \(startLong) is long")
}
})
}
Hello, here is a geocoding function that I created, that simply takes an address and returns the address's coordinates. My problem is, that at the end of the code section, when I do print(endLat) it prints out 0, however when I do it in the dispatch_async in the function, it comes out to the proper latitude.
I realize this is an asynchronous function, which is why I tried to use the dispatch_async to update the variable instantly.
My question is, how can I have these variables update instantly? Thanks in advance.
You've got a little confused with what you mean by "Asynchronous function not updating variable". forwardGeocodingStarting(_:) is NOT an Async function. geocodeAddressString is an async function designed by the API to allow you to use the following values you get after the operation is done within that function only. Hence you keep getting 0 as the it doesn't wait till the operation is done completely to display the value. dispatch_async(dispatch_get_main_queue) is used only to update UI on the Main thread. You've to use a completion handler in your function like so:
typealias theDouble = (Double, Double) -> ()
func forwardGeocodingStarting(address: String, completion: theDouble){
//your code
let aVar: Double!
let bVar: Double!
let placemark = placemarks?[0]
let location = placemark?.location
let coordinate = location?.coordinate
aVar = (coordinate?.latitude)!
bVar = (coordinate?.longitude)!
completion(aVar, bVar)
}
Now when you call your function, update the variables startLat and startLong like so:
forwardGeocodingStarting(<your string address>) { firstVar, secondVar in
startLat = firstVar
startLong = secondVar
print("\(startLat) is lat and \(startLong) is long") //You will get correct values
}
You cannot have these variables update instantaneously, as you are calling an asynchronous function. Thats an obvious contradiction :-)
That you dispatch back to the main queue is fine, but that doesn't change the asynchronous behaviour. In fact your are even issuing yet another dispatch_async ... (meaning the body of that code will get run at some later point in time)
You could wait for the results to be available, e.g. using a dispatch group, but quite likely that is not what you want here.
I assume you want to use the values in some view controller? In this case you probably fill in some instance variables as the results arrive and update the UI state once both values are available.
"Walltime" is a little-known time format used by Grand Central Dispatch. Apple talks about it here:
https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/
There are some things it's really handy for, though, but it's a sticky wicket. It's hard to make it play nice with other time formats, which is what my question's about.
I can make a walltime by turning an NSDate into a timespec, and then using with dispatch_walltime:
let now = NSDate().timeIntervalSince1970
let nowWholeSecsFloor = floor(now)
let nowNanosOnly = now - nowWholeSecsFloor
let nowNanosFloor = floor(nowNanosOnly * Double(NSEC_PER_SEC))
var thisStruct = timespec(tv_sec: Int(nowWholeSecsFloor),
tv_nsec: Int(nowNanosFloor))
let wallTime = dispatch_walltime(& thisStruct, 0)
But lord love a duck, I can't figure out how to get it back into an NSDate. Here's my try:
public func toNSDate(wallTime: dispatch_time_t)->NSDate {
let wallTimeAsSeconds = Double(wallTime) / Double(NSEC_PER_SEC)
let date = NSDate(timeIntervalSince1970: wallTimeAsSeconds)
return date
}
The resulting NSDate is not just off, but somewhat hilariously off, like five hundred years or something. As Martin R pointed out, the problem is that dispatch_time_t is an opaque value, with an undocumented representation of time.
Does anyone know how to do this?
EDIT: if the process of creating the walltime is confusing, this is basically what's going on:
NSDate defines time with a Double, and everything after the decimal point is the nanoseconds. dispatch_time, which can create a walltime, defines time with UInt64, so you have to convert between Double and UInt64 to use it. To do that conversion you need to use a timespec, which takes seconds and nanoseconds as separate arguments, each of which must be Int.
A whole lotta convertin' going on!
The real answer is: you can't.
In the "time.h" header file it is stated:
/*!
* #typedef dispatch_time_t
*
* #abstract
* A somewhat abstract representation of time; where zero means "now" and
* DISPATCH_TIME_FOREVER means "infinity" and every value in between is an
* opaque encoding.
*/
typedef uint64_t dispatch_time_t;
So dispatch_time_t uses an undocumented "abstract" representation of time, which
may even change between releases.
That being said, let's have some fun and try to figure out what
a dispatch_time_t really is. So we have a look at "time.c", which contains the implementation of
dispatch_walltime():
dispatch_time_t
dispatch_walltime(const struct timespec *inval, int64_t delta)
{
int64_t nsec;
if (inval) {
nsec = inval->tv_sec * 1000000000ll + inval->tv_nsec;
} else {
nsec = (int64_t)_dispatch_get_nanoseconds();
}
nsec += delta;
if (nsec <= 1) {
// -1 is special == DISPATCH_TIME_FOREVER == forever
return delta >= 0 ? DISPATCH_TIME_FOREVER : (dispatch_time_t)-2ll;
}
return (dispatch_time_t)-nsec;
}
The interesting part is the last line: it takes the negative value of the
nanoseconds, and this value is cast back to an (unsigned) dispatch_time_t. There are also some special cases.
Therefore, to reverse the conversion, we have to negate the
dispatch_time_t and take that as nanoseconds:
public func toNSDate(wallTime: dispatch_time_t)->NSDate {
// Tricky part HERE:
let nanoSeconds = -Int64(bitPattern: wallTime)
// Remaining part as in your question:
let wallTimeAsSeconds = Double(nanoSeconds) / Double(NSEC_PER_SEC)
let date = NSDate(timeIntervalSince1970: wallTimeAsSeconds)
return date
}
And indeed, this converts the walltime correctly back to the original
NSDate, at least when I test it in an OS X application.
But again: don't do it! You would rely on an undocumented representation which could change between OS releases. There may also
be special cases that are not considered in the above code.
Also the representation in the iOS runtime could be different, I did
not try that.
You have been warned!