George Yacoub
George Yacoub

Reputation: 1456

How can I set timestamps accurately for midi events?

I am parsing a midi file and then sending midi events to connected synthesizer using MIKMIDI but I am getting the events timestamps rounded to an integral second rather than more specific milliseconds. I need help calculating the time accurately. Thanks. // defined earlier class Note { var symbol:String var octave:Int var midiValue:Int var duration:Float }

let BPM:Double = 75
// Ode to joy simplified
var song:[Note] = [
  Note(symbol: "E", octave: 5),
  Note(symbol: "E", octave: 5),
  Note(symbol: "F", octave: 5),
  Note(symbol: "G", octave: 5),

  Note(symbol: "G", octave: 5),
  Note(symbol: "F", octave: 5),
  Note(symbol: "E", octave: 5),
  Note(symbol: "D", octave: 5),

  Note(symbol: "C", octave: 5),
  Note(symbol: "C", octave: 5),
  Note(symbol: "D", octave: 5),
  Note(symbol: "E", octave: 5),

  Note(symbol: "E", octave: 5, duration: 3/8),
  Note(symbol: "D", octave: 5, duration: 1/8),
  Note(symbol: "D", octave: 5, duration: 1/2)
]
let now = NSDate()
let totalDuration:Double = 0
for note:Note in song {
  let fullNoteValue:Double = (60000 / BPM) * 0.001 * 4
  let noteDuration:Double = fullNoteValue * Double(note.duration)
  let timestamp = now.dateByAddingTimeInterval(noteDuration + totalDuration)
  MidiDevice.sharedInstance().playNoteOn(note.midiValue, withTimestamp: timestamp)
  totalDuration += noteDuration
}

MidiDevice playNoteOn definition

- (void)playNoteOn:(NSInteger)note withTimestamp:(MIDITimeStamp)timestamp {
  MIKMutableMIDINoteOnCommand *noteOn = [MIKMutableMIDINoteOnCommand commandForCommandType:MIKMIDICommandTypeNoteOn];
  noteOn.note = (NSUInteger) note;
  noteOn.velocity = 100;
  noteOn.timestamp = timestamp
  // noteOn.midiTimestamp = timestamp;

  NSMutableArray *destinations = [NSMutableArray array];

  for (MIKMIDIDevice *device in [[MIKMIDIDeviceManager sharedDeviceManager] availableDevices]) {
    for (MIKMIDIEntity *entity in [device.entities valueForKeyPath:@"@unionOfArrays.destinations"]) {
      [destinations addObject:entity];
    }
  }

  //  NSArray *destinations = [self.device.entities valueForKeyPath:@"@unionOfArrays.destinations"];
  if (![destinations count]) return;
  for (MIKMIDIDestinationEndpoint *destination in destinations) {
    NSError *error = nil;
    if(![self.deviceManager sendCommands:@[noteOn] toEndpoint:destination error:&error]) {
      NSLog(@"Unable to send command %@ to endpoint %@: %@", noteOn, destination, error);
    }
  }
}

How do I change this timestamp to an accurate one I can send using MIDISend() or MIKMIDI?

Upvotes: 0

Views: 1146

Answers (1)

matt
matt

Reputation: 534885

You are using incompatible time measurements. First you say this:

let now = NSDate()
let timestamp = now.dateByAddingTimeInterval(noteDuration + totalDuration)

But then you hand that to the midi device:

 MidiDevice.sharedInstance().playNoteOn(note.midiValue, withTimestamp: timestamp)

But the timestamp here, it turns out, is supposed to be a MIDITimeStamp:

(void)playNoteOn:(NSInteger)note withTimestamp:(MIDITimeStamp)timestamp {

A MIDITimeStamp is not an NSDate! MIDITimeStamp is based on mach_absolute_time(), a completely different kind of measure. An NSDate is measured as an NSTimeInterval, which is a Double counting seconds from some fixed reference date-time. Mach absolute time is a huge integer counting nanoseconds from when the computer was turned on!

So, to call this method successfully, you need to get rid of the NSDate and think in MIDI time.

Upvotes: 2

Related Questions