Sanford.Multimedia.Midi
Sanford.Multimedia.Midi copied to clipboard
Changing the tempo
I would like to be able to speed up and slow down a song as it's playing. I've seen a number of posts requesting help with Tempo, but never saw any solutions to this problem.
I have found a get/set method in the class: Sanford.Multimedia.Midi.MidiInternalClock for the MidiInternalClock values, but I am unable to access both the Tempo and MidiInternalClock from my code.
How would one go about implementing changing the tempo of a playing sequence?
Hello,
This is what I did. Hope it will help. You need to stop the player, remove all tempo events, insert the new tempo, and restart the player. Fabrice
In the form where you need to speed up or slow down the tempo (new tempo value is _tempo):
// Stop sequencer if it was playing // PlayerState is an enum to manage "play", "pause", "stop" if (PlayerState == PlayerStates.Playing) sequencer1.Stop();
// Remove all tempo events foreach (Track trk in sequence1.tracks) { trk.RemoveTempoEvent(); }
// Insert new tempo event in track 0 sequence1.tracks[0].insertTempo(_tempo);
// Restart sequencer if it was playing if (PlayerState == PlayerStates.Playing) sequencer1.Continue();
I added some code to the module Track.cs of Sanford.Multimedia.Midi\Sequencing\Track Classes in order to be able to remove tempo events and insert a tempo event.
///
///
while (current.AbsoluteTicks <= Length)
{
IMidiMessage a = current.MidiMessage;
if (a.MessageType == MessageType.Meta)
{
MetaMessage Msg = (MetaMessage)current.MidiMessage;
if (Msg.MetaType == MetaType.Tempo)
{
return id;
}
else
{
#region next
if (current.Next != null)
{
current = current.Next;
id++;
}
else
{
break;
}
#endregion next
}
}
else
{
#region next
if (current.Next != null)
{
current = current.Next;
id++;
}
else
{
break;
}
#endregion next
}
}
return -1;
}
///
You can try this: add this to Sequencer.cs
public int Tempo
{
get
{
return this.clock.Tempo;
}
set
{
this.clock.Tempo = value;
}
}
Now you can get or set tempo while playing. Default tempo (120bmp) is 500000
By far simpler than my answer :-) Thank you duskozi
MIDI files can have none, one or large number of Tempo events. So it is a good thing to use HandleMetaMaessagePlayed for catching tempo events, get the tempo and use some kind of ratio to get the new tempo for slower or faster playback. Example: midi file has 3 tempo events, for all 3 calculate new tempos to have 50% slower playback in regards to original.
I changed the MidiFile.play method by introducing a "scalar". I simply modify the time based on a scale value. Works great for what I need to do.
def play(self, meta_messages=False, sync_to_system_clock=False, starting_message_number=0):
"""Play back all tracks.
The generator will sleep between each message by
default. Messages are yielded with correct timing. The time
attribute is set to the number of seconds slept since the
previous message.
By default you will only get normal MIDI messages. Pass
meta_messages=True if you also want meta messages.
You will receive copies of the original messages, so you can
safely modify them without ruining the tracks.
"""
sleep = time.sleep
self.starting_message_number = starting_message_number
for msg in self:
sleep_time = msg.time
if self.tempo_scaler != 1.0: # if tempo is set by user
sleep_time = msg.time*self.tempo_scaler
sleep(sleep_time)
if isinstance(msg, MetaMessage) and not meta_messages:
continue
else:
yield msg ########## YIELD'S HERE! ##########