public void Play(double durationMS, double frequency, float volume) { started = GetUnixTime(); duration = durationMS; if (debugMode) { new Beep((int)frequency, (int)duration, volume).Play(); } else { OpenVRHelper.PlayNote(controller, (float)durationMS / 1000F, (float)frequency, volume); } }
static void Run(Settings settings) { if (!OpenVRHelper.InitHMD() && !settings.Debug) { return; } Console.WriteLine("Found {0} devices", OpenVRHelper.DeviceCount()); string inputFile = settings.InputFile; if (!File.Exists(inputFile)) { Console.WriteLine("Input file {0} doesn't exist!", inputFile); return; } MidiFile midi = new MidiFile(inputFile, false); Console.WriteLine("Found {0} devices", OpenVRHelper.DeviceCount()); Player[] players = new Player[OpenVRHelper.DeviceCount()]; for (int i = 0; i < players.Count(); i++) { players[i] = new Player(i, settings.Tolerance, settings.Debug); } //these will bet set correctly before any notes are played double bpm = 120; double ticksPerQuarter = 1; double msPerQuarter = 1; int divison = midi.DeltaTicksPerQuarterNote; int[] trackEventIndex = new int[midi.Events.Count()]; long currentTick = 0; Stopwatch sw = Stopwatch.StartNew(); int tracksStopped = 0; //based on https://github.com/ipatix/serialmidi/blob/master/serialmidi/Program.cs //and https://gitlab.com/Pilatomic/SteamControllerSinger/blob/master/main.cpp while (true) { for (int trackNum = 0; trackNum < midi.Events.Count(); trackNum++) { var track = midi.Events[trackNum]; if (trackEventIndex[trackNum] >= track.Count()) { continue; } while (currentTick >= track[trackEventIndex[trackNum]].AbsoluteTime) { var ev = track[trackEventIndex[trackNum]]; if (ev.CommandCode == MidiCommandCode.NoteOn) { NoteOnEvent note = (NoteOnEvent)ev; if (note.OffEvent == null) { Console.WriteLine("Note {0} doesn't have an off event, skipping.", note.NoteName); } else { //calculate note duration double duration = note.NoteLength * msPerQuarter; //calculate frequency from the note number //https://pages.mtu.edu/~suits/NoteFreqCalcs.html double frequency = (440F * Math.Pow(2, (note.NoteNumber - 69) / 12F)); for (int i = 0; i < players.Count(); i++) { if (players[i].IsBusy()) { if (i == players.Count() - 1) { Console.WriteLine("Note {0} can't be played because both controllers are busy. Consider changing this part of the song or increasing tolerance time (-t).", note.NoteName); } continue; } Console.WriteLine("Controller {0}: note {1} ({2:0.##} Hz) for {3:0.##} ms", i, note.NoteName, frequency, duration); players[i].Play(duration, frequency, settings.Volume); break; } } } if (ev.CommandCode == MidiCommandCode.MetaEvent) { MetaEvent meta = (MetaEvent)ev; if (meta.MetaEventType == MetaEventType.SetTempo) { TempoEvent tempoEvent = (TempoEvent)meta; bpm = tempoEvent.Tempo; if (settings.Debug) { Console.WriteLine("New tempo: " + bpm + " BPM"); } //https://stackoverflow.com/questions/2038313/converting-midi-ticks-to-actual-playback-seconds ticksPerQuarter = (60 * Stopwatch.Frequency) / (bpm * divison); msPerQuarter = (60000F / (bpm * divison)); } } trackEventIndex[trackNum]++; if (trackEventIndex[trackNum] == track.Count) { tracksStopped++; if (settings.Debug) { Console.WriteLine("Stopped track {0}/{1}", tracksStopped, midi.Events.Count()); } break; } } } if (tracksStopped == midi.Events.Count()) { break; } currentTick++; //just skip cycles for now while (sw.ElapsedTicks < ticksPerQuarter) { ; } sw.Restart(); } sw.Stop(); Console.WriteLine("Stopped"); OpenVRHelper.Shutdown(); }