Carriable/BaseInstrument.Playback.cs
using System;
using Clover.Utilities;
namespace Clover.Carriable;
public partial class BaseInstrument
{
[Sync] public bool IsPlayingBack { get; set; }
[Sync] public string PlaybackTitle { get; set; } = "";
// [Sync] public NetList<bool> PlaybackTracksEnabled { get; set; } = new();
[Sync] public NetList<BaseInstrument> PlaybackTracksInstruments { get; set; } = new();
[Sync] public int TransposePlayback { get; set; } = 0;
// private TimeUntil _nextPlaybackEntry;
[Sync] public TimeSince PlaybackStarted { get; set; }
public float PlaybackProgress;
// private int _playbackTimeSignature = 4;
private int _playbackTempo = 0;
private MidiFile _midiFile;
// private int _currentIndex;
private List<int> _currentTrackEventIndices = new();
private List<float> _currentTrackVolumes = new();
// private int _playbackTrackIndex;
private void Playback()
{
var finished = 0;
for ( var trackIndex = 0; trackIndex < _midiFile.Tracks.Length; trackIndex++ )
{
var track = _midiFile.Tracks[trackIndex];
// TODO: play multiple tracks at once or just one?
/*if ( trackIndex != _playbackTrackIndex )
{
continue;
}*/
// Log.Info( $"{_currentTrackEventIndices[trackIndex]} / {track.MidiEvents.Count}" );
if ( _currentTrackEventIndices[trackIndex] >= track.MidiEvents.Count )
{
finished++;
continue;
}
for ( var i = _currentTrackEventIndices[trackIndex]; i < track.MidiEvents.Count; i++ )
{
// skip if track is disabled but keep track of the current index
var midiEvent = track.MidiEvents[i];
/*if ( midiEvent.MidiEventType != MidiEventType.NoteOn )
{
Log.Info( $"Event: {midiEvent.MidiEventType} {midiEvent.Time}" );
if ( midiEvent.MidiEventType == MidiEventType.MetaEvent )
{
if ( midiEvent.MetaEventType == MetaEventType.Tempo )
{
_playbackTempo = midiEvent.Arg2;
Log.Info( $"Tempo change: {midiEvent.Arg1}, {midiEvent.Arg2}, {midiEvent.Arg3}" );
}
}
_currentTrackEventIndices[trackIndex] = i + 1;
continue;
}*/
var bps = 60.0f / _playbackTempo;
var time = ((float)midiEvent.Time / (float)_midiFile.TicksPerQuarterNote) * bps;
if ( time > PlaybackStarted )
{
break;
}
if ( trackIndex > PlaybackTracksInstruments.Count || !PlaybackTracksInstruments[trackIndex].IsValid() )
{
_currentTrackEventIndices[trackIndex] = i + 1;
continue;
}
/*if ( i <= _currentIndex )
{
continue;
}*/
/*if ( i < _currentTrackEventIndices[trackIndex] )
{
continue;
}*/
if ( midiEvent.MidiEventType == MidiEventType.NoteOn )
{
var channel = midiEvent.Channel;
var note = midiEvent.Note;
var velocity = midiEvent.Velocity;
// Log.Info( $"Note on: {note} {velocity} @ {midiEvent.Time}" );
PlayMidiNote( trackIndex, note, velocity );
}
else if ( midiEvent.MidiEventType == MidiEventType.NoteOff )
{
var channel = midiEvent.Channel;
var note = midiEvent.Note;
var velocity = midiEvent.Velocity;
// Log.Info( $"Note off: {note} {velocity} @ {midiEvent.Time}" );
}
else if ( midiEvent.MidiEventType == MidiEventType.MetaEvent )
{
if ( midiEvent.MetaEventType == MetaEventType.Tempo )
{
var tempo = midiEvent.Arg1;
var beatsPerMinute = midiEvent.Arg2;
_playbackTempo = beatsPerMinute;
Log.Info( $"Tempo change: {midiEvent.Arg1}, {midiEvent.Arg2}, {midiEvent.Arg3}" );
}
}
else if ( midiEvent.MidiEventType == MidiEventType.PitchBendChange )
{
var channel = midiEvent.Arg1;
var value1 = midiEvent.Arg2;
var value2 = midiEvent.Arg3;
Log.Info( $"Pitch bend: {channel} {value1} {value2}" );
}
else if ( midiEvent.MidiEventType == MidiEventType.ProgramChange )
{
var channel = midiEvent.Arg1;
var program = midiEvent.Arg2;
Log.Info( $"Program change: {channel} {program}" );
}
else if ( midiEvent.MidiEventType == MidiEventType.ChannelAfterTouch )
{
var channel = midiEvent.Arg1;
var value = midiEvent.Arg2;
Log.Info( $"Channel after touch: {channel} {value}" );
}
else if ( midiEvent.MidiEventType == MidiEventType.KeyAfterTouch )
{
var channel = midiEvent.Arg1;
var note = midiEvent.Arg2;
var value = midiEvent.Arg3;
Log.Info( $"Key after touch: {channel} {note} {value}" );
}
else if ( midiEvent.MidiEventType == MidiEventType.ControlChange )
{
var channel = midiEvent.Arg1;
var controlChangeType = midiEvent.ControlChangeType;
var value = midiEvent.Value;
Log.Info( $"Control change: {channel} {controlChangeType} {value}" );
if ( controlChangeType == ControlChangeType.Volume )
{
_currentTrackVolumes[trackIndex] = value / 127.0f;
}
}
else
{
Log.Info( $"Event: {midiEvent.MidiEventType} {midiEvent.Time}" );
}
// _currentIndex = i;
_currentTrackEventIndices[trackIndex] = i + 1;
}
}
if ( finished >= _midiFile.Tracks.Length )
{
IsPlayingBack = false;
Log.Info( "Finished playback" );
return;
}
CalculateProgress();
}
private void CalculateProgress()
{
// add all the ticks from all the tracks
var totalTicks = _midiFile.Tracks.Sum( x => x.MidiEvents.Count );
// add all the ticks from the current track
var currentTicks = _currentTrackEventIndices.Sum( x => x );
// calculate the progress
PlaybackProgress = (float)currentTicks / (float)totalTicks;
// Log.Info( $"Progress: {PlaybackProgress}, {currentTicks} / {totalTicks}" );
}
/// <summary>
/// Play a midi note to the instrument, converting the midi note to a note enum
/// </summary>
/// <param name="note"></param>
private void PlayMidiNote( int trackIndex, int note, int velocity )
{
if ( TransposePlayback != 0 )
{
note += TransposePlayback;
}
var octave = note / 12;
var noteIndex = note % 12;
Note noteEnum = (Note)noteIndex;
var volume = velocity / 127.0f;
volume *= _currentTrackVolumes[trackIndex];
var instrument = PlaybackTracksInstruments[trackIndex];
if ( !instrument.IsValid() )
{
PlaybackTracksInstruments[trackIndex] = this;
instrument = this;
}
instrument.PlayNote( octave, noteEnum, volume );
}
/*public void StartPlayback( string file )
{
_isPlayingBack = true;
_nextPlaybackEntry = 0.0f;
LoadFile( file );
}*/
public void LoadFile( string file )
{
IsPlayingBack = false;
Log.Info( $"Loading midi file: {file}" );
var midiFile = new MidiFile( $"midi/{file}" );
// 0 = single-track, 1 = multi-track, 2 = multi-pattern
var midiFileformat = midiFile.Format;
Log.Info( $"Midi file format: {midiFileformat}" );
// also known as pulses per quarter note
var ticksPerQuarterNote = midiFile.TicksPerQuarterNote;
Log.Info( $"Ticks per quarter note: {ticksPerQuarterNote}" );
Log.Info( $"Tracks: {midiFile.Tracks.Length}" );
foreach ( var track in midiFile.Tracks )
{
foreach ( var midiEvent in track.MidiEvents )
{
if ( midiEvent.MidiEventType == MidiEventType.NoteOn )
{
var channel = midiEvent.Channel;
var note = midiEvent.Note;
var velocity = midiEvent.Velocity;
// Log.Info( $"Note on: {note} {velocity} @ {midiEvent.Time}" );
}
if ( midiEvent.MidiEventType == MidiEventType.MetaEvent )
{
Log.Info(
$"Meta event: {midiEvent.MetaEventType} - {midiEvent.Arg1} {midiEvent.Arg2} {midiEvent.Arg3}" );
}
}
foreach ( var textEvent in track.TextEvents )
{
if ( textEvent.TextEventType == TextEventType.Lyric )
{
var time = textEvent.Time;
var text = textEvent.Value;
// Log.Info( $"Lyric: {time} {text}" );
}
}
}
_midiFile = midiFile;
PlaybackStarted = 0.0f;
// IsPlayingBack = true;
// _currentIndex = 0;
// _transposePlayback = -12;
PlaybackTracksInstruments.Clear();
_currentTrackEventIndices.Clear();
foreach ( var track in _midiFile.Tracks )
{
_currentTrackEventIndices.Add( 0 );
PlaybackTracksInstruments.Add( this );
}
PlaybackTitle = file;
/*_tracksEnabled.Clear();
foreach ( var track in _midiFile.Tracks )
{
_tracksEnabled.Add( false );
}
_tracksEnabled[0] = true;*/
}
public void StartPlayback()
{
_currentTrackEventIndices.Clear();
_currentTrackEventIndices.AddRange( Enumerable.Repeat( 0, _midiFile.Tracks.Length ) );
_currentTrackVolumes.Clear();
_currentTrackVolumes.AddRange( Enumerable.Repeat( 1.0f, _midiFile.Tracks.Length ) );
PlaybackStarted = 0.0f;
IsPlayingBack = true;
}
public void StopPlayback()
{
IsPlayingBack = false;
PlaybackStarted = 0.0f;
PlaybackProgress = 0.0f;
}
public void ToggleTrackPlayback( int index )
{
if ( index >= PlaybackTracksInstruments.Count )
{
return;
}
// PlaybackTracksEnabled[index] = !PlaybackTracksEnabled[index];
if ( PlaybackTracksInstruments[index] == this )
{
PlaybackTracksInstruments[index] = null;
}
else
{
PlaybackTracksInstruments[index] = this;
}
// _currentTrackEventIndices[index] = 0;
}
public bool IsPlaybackTrackEnabled( int index )
{
if ( index >= PlaybackTracksInstruments.Count )
{
return false;
}
return PlaybackTracksInstruments[index].IsValid();
}
[Rpc.Owner]
public void RequestTrackPlayback( BaseInstrument instrument, int trackIndex )
{
if ( instrument == this )
{
return;
}
if ( trackIndex >= PlaybackTracksInstruments.Count )
{
return;
}
Log.Info( $"Player {Rpc.Caller.DisplayName} added themselves to track {trackIndex}" );
PlaybackTracksInstruments[trackIndex] = instrument;
}
}