Beispiel #1
0
        public Task OpenAsync()
        {
            if (synth != null)
            {
                throw new InvalidOperationException("The MIDI output is already open.");
            }
            settings = new Settings();
            if (midi_access.ConfigureSettings != null)
            {
                midi_access.ConfigureSettings(settings);
            }
            synth             = new Synth(settings);
            synth.HandleError = midi_access.HandleNativeError;
            foreach (var factory in midi_access.SoundFontLoaderFactories)
            {
                synth.AddSoundFontLoader(factory(synth));
            }
            foreach (var sf in midi_access.SoundFonts)
            {
                synth.LoadSoundFont(sf, false);
            }

            adriver = new AudioDriver(synth.Settings, synth);

            return(Task.FromResult(true));
        }
Beispiel #2
0
 private static void Prefix(AudioDriver __instance)
 {
     if (AudicaMod.skipQueued)
     {
         AudicaMod.SkipIntro();
     }
 }
Beispiel #3
0
 public FluidMusicSystem()
 {
     Settings settings = new Settings();
     this.synth = new Synthesizer(settings);
     this.driver = new AudioDriver(settings,synth);
     synth.SFontLoad("sndfont2.sf2");
 }
Beispiel #4
0
        private static float CalculateIntensity(float startTick, float endTick, List <SongCues.Cue> cues)
        {
            float intensity = 0f;
            bool  indexSet  = false;

            for (int i = startIndex; i < cues.Count; i++)
            {
                SongCues.Cue cue = SongCues.I.mCues.cues[i];
                if (cue.tick >= endTick)
                {
                    break;
                }
                if (cue.tick >= startTick && cue.tick < endTick)
                {
                    intensity += GetTargetAmount((Hitsound)cue.velocity, cue.behavior);
                    if (!indexSet)
                    {
                        indexSet   = true;
                        startIndex = i;
                    }
                }
            }

            intensity /= AudioDriver.TickSpanToMs(SongDataHolder.I.songData, startTick, endTick);

            intensity *= 1000f;
            return(intensity);
        }
Beispiel #5
0
        public FluidMusicSystem()
        {
            Settings settings = new Settings();

            this.synth  = new Synthesizer(settings);
            this.driver = new AudioDriver(settings, synth);
            synth.SFontLoad("sndfont2.sf2");
        }
Beispiel #6
0
 private static void Postfix(AudioDriver __instance)
 {
     if (UI.loaded)
     {
         ScoreOverlayMod.ResetTracker();
         UI.FadeInOverlay();
         UI.UpdateUiInfo(SongDataHolder.I.songData);
     }
 }
 public void EvaluateCues(SongCues.Cue[] cues, SongList.SongData songData)
 {
     this.length = AudioDriver.TickSpanToMs(songData, cues[0].tick, cues[cues.Length - 1].tick);
     SplitCues(cues);
     CalculateSpacing();
     CalculateDensity();
     CalculateReadability();
     difficultyRating = ((spacing + readability) / length) * 500f + (length / 100000f * lengthMultiplier);
 }
Beispiel #8
0
 public static void Main(string[] args)
 {
     using (var settings = new Settings())
     {
         // Change this if you don't have pulseaudio or want to change to anything else.
         if (Environment.OSVersion.Platform == PlatformID.Unix)
         {
             settings[ConfigurationKeys.AudioDriver].StringValue = "pulseaudio";
         }
         settings[ConfigurationKeys.SynthAudioChannels].IntValue = 2;
         using (var syn = new Synth(settings))
         {
             foreach (var arg in args)
             {
                 if (SoundFont.IsSoundFont(arg))
                 {
                     syn.LoadSoundFont(arg, true);
                 }
             }
             if (syn.FontCount == 0)
             {
                 syn.LoadSoundFont("/usr/share/sounds/sf2/FluidR3_GM.sf2", true);
             }
             for (int i = 0; i < 16; i++)
             {
                 syn.SoundFontSelect(i, 0);
             }
             var files = args.Where(SoundFont.IsMidiFile);
             if (files.Any())
             {
                 foreach (var arg in files)
                 {
                     using (var player = new Player(syn))
                     {
                         using (var adriver = new AudioDriver(syn.Settings, syn))
                         {
                             player.Add(arg);
                             player.Play();
                             player.Join();
                         }
                     }
                 }
             }
             else
             {
                 using (var adriver = new AudioDriver(syn.Settings, syn))
                 {
                     syn.ProgramChange(0, 1);
                     syn.NoteOn(0, 60, 120);
                     Thread.Sleep(5000);
                     syn.NoteOff(0, 60);
                 }
             }
         }
     }
 }
 private static void Postfix(AudioDriver __instance)
 {
     foreach (string str in SongRequests.requestList.ToList())
     {
         if (str == SongRequests.selectedSong.songID)
         {
             SongRequests.requestList.Remove(str);
         }
     }
 }
Beispiel #10
0
 private static void Postfix(AudioDriver __instance)
 {
     for (int i = 0; i <  AudicaMod.requestList.Count; i++)
     {
         if (AudicaMod.requestList[i] == AudicaMod.selectedSong.songID)
         {
             AudicaMod.requestList.RemoveAt(i);
         }
     }
 }
 public void LoadUnloadSoundFont()
 {
     using (var syn = new Synth (NewAlsaSettings ()))
     using (var audio = new AudioDriver (syn.Settings, syn)) {
         syn.LoadSoundFont ("/usr/share/sounds/sf2/FluidR3_GS.sf2", false);
         Assert.AreEqual (1, syn.FontCount, "FontCount");
         for (int i = 0; i < 16; i++)
             syn.SoundFontSelect (i, 1);
         syn.UnloadSoundFont (1, true);
         Assert.AreEqual (0, syn.FontCount, "FontCount");
     }
 }
Beispiel #12
0
 public void LoadUnloadSoundFont()
 {
     using (var syn = new Synth(NewAlsaSettings()))
         using (var audio = new AudioDriver(syn.Settings, syn)) {
             syn.LoadSoundFont("/usr/share/sounds/sf2/FluidR3_GS.sf2", false);
             Assert.AreEqual(1, syn.FontCount, "FontCount");
             for (int i = 0; i < 16; i++)
             {
                 syn.SoundFontSelect(i, 1);
             }
             syn.UnloadSoundFont(1, true);
             Assert.AreEqual(0, syn.FontCount, "FontCount");
         }
 }
    private void Awake()
    {
        S = this;
        generateAudioSources(); //MUST BE CALLED BEFORE ANY SOUNDS ARE MADE


        if (SceneManager.GetActiveScene().name == "Main")
        {
            scene = ActiveScene.Main;
        }
        else
        {
            scene = ActiveScene.Menu;
        }
    }
Beispiel #14
0
        public ServerCommunication(TcpClient client)
        {
            id   = Guid.Empty;
            text = new TCPDuplexCommunication(client);
            text.MessageReceived += OnTextMessageReceived;
            text.StartListening();

            UdpClient udpClient = new UdpClient();

            udpClient.Connect("localhost", 13001);
            audio = new UDPDuplexCommunication(udpClient, (IPEndPoint)client.Client.RemoteEndPoint);

            audioDriver = new AudioDriver();
            audioDriver.StartRecording();
            audioDriver.DataAvailable += OnAudioDataAvailable;
        }
Beispiel #15
0
            private static void Prefix(AudioDriver __instance)
            {
                if (KataConfig.I.practiceMode)
                {
                    return;
                }

                if (GrindMode.waitForRestart)
                {
                    GrindMode.waitForRestart = false;
                }
                if (GrindMode.skipQueued || Config.autoSkip)
                {
                    GrindMode.SkipIntro();
                }
            }
        public FluidsynthMidiReceiver(Context context)
        {
#if MIDI_MANAGER
            access = new FluidsynthMidiAccess();
            access.ConfigureSettings = (settings) => {
#else
            var settings = new Settings();
#endif
                settings [ConfigurationKeys.AudioDriver].StringValue = "oboe";

                //settings [ConfigurationKeys.SynthParallelRender].IntValue = 0;
                //settings [ConfigurationKeys.SynthThreadsafeApi].IntValue = 0;
                //settings [ConfigurationKeys.AudioPeriods].IntValue = 16;
                //settings [ConfigurationKeys.AudioPeriods].IntValue = 64;

                settings [ConfigurationKeys.AudioSampleFormat].StringValue = "16bits";         // float or 16bits

                var manager = context.GetSystemService(Context.AudioService).JavaCast <AudioManager> ();

                // Note that it is NOT audio sample rate but *synthesizing* sample rate.
                // So it is kind of wrong assumption that AudioManager.PropertyOutputSampleRate would give the best outcome...
                //var sr = double.Parse (manager.GetProperty (AudioManager.PropertyOutputSampleRate));
                //settings [ConfigurationKeys.SynthSampleRate].DoubleValue = sr;
                settings [ConfigurationKeys.SynthSampleRate].DoubleValue = 11025;

                var fpb = double.Parse(manager.GetProperty(AudioManager.PropertyOutputFramesPerBuffer));
                settings [ConfigurationKeys.AudioPeriodSize].IntValue = (int)fpb;
#if MIDI_MANAGER
            };
            SynthAndroidExtensions.GetSoundFonts(access.SoundFonts, context, predefined_temp_path);
            output = access.OpenOutputAsync(access.Outputs.First().Id).Result;
#else
                syn = new Synth(settings);
                var sfs = new List <string> ();
                SynthAndroidExtensions.GetSoundFonts(sfs, context, predefined_temp_path);

                asset_sfloader = new AndroidNativeAssetSoundFontLoader(settings, context.Assets);
                syn.AddSoundFontLoader(asset_sfloader);
                foreach (var sf in sfs)
                {
                    syn.LoadSoundFont(sf, false);
                }

                adriver = new AudioDriver(syn.Settings, syn);
#endif
        }
Beispiel #17
0
 public static void Main(string[] args)
 {
     using (var settings = new Settings()) {
         if (Environment.OSVersion.Platform == PlatformID.Unix)
         {
             settings [ConfigurationKeys.AudioDriver].StringValue = "alsa";
         }
         using (var syn = new Synth(settings)) {
             foreach (var arg in args)
             {
                 if (Synth.IsSoundFont(arg))
                 {
                     syn.LoadSoundFont(arg, true);
                 }
             }
             if (syn.FontCount == 0)
             {
                 syn.LoadSoundFont("/usr/share/sounds/sf2/FluidR3_GM.sf2", true);
             }
             var files = args.Where(a => Synth.IsMidiFile(a));
             if (files.Any())
             {
                 foreach (var arg in files)
                 {
                     using (var player = new Player(syn)) {
                         using (var adriver = new AudioDriver(syn.Settings, syn)) {
                             player.Add(arg);
                             player.Play();
                             player.Join();
                         }
                     }
                 }
             }
             else
             {
                 using (var adriver = new AudioDriver(syn.Settings, syn)) {
                     syn.NoteOn(0, 60, 100);
                     Thread.Sleep(5000);
                     syn.NoteOff(0, 60);
                 }
             }
         }
     }
 }
Beispiel #18
0
        private static void Postfix(AudioDriver __instance)
        {
            if (!Config.Enabled)
            {
                return;
            }
            SongCues.Cue[]    cues = SongCues.I.GetCues();
            SongList.SongData song = SongList.I.GetSong(SongDataHolder.I.songData.songID);
            SongList.SongData.TempoChange[] tempos = song.tempos;

            for (int i = 0; i < tempos.Length; i++)
            {
                float timingWindowMs = 200 * Mathf.Lerp(0.07f, 1.0f, percent);

                float ticks     = timingWindowMs / (60000 / (tempos[i].tempo * 480));
                float halfTicks = ticks / 2;

                for (int j = 0; j < cues.Length; j++)
                {
                    if (cues[j].behavior != Target.TargetBehavior.Chain && cues[j].behavior != Target.TargetBehavior.Dodge && cues[j].behavior != Target.TargetBehavior.Melee)
                    {
                        void UpdateTarget(SongCues.Cue cue)
                        {
                            cue.slopAfterTicks  = halfTicks;
                            cue.slopBeforeTicks = halfTicks;
                        }

                        if (cues[j].tick >= tempos[i].tick)
                        {
                            if (tempos.Length >= tempos.Length + 1 && cues[j].tick < tempos[i + 1].tick)
                            {
                                UpdateTarget(cues[j]);
                            }
                            else if (tempos.Length < tempos.Length + 1)
                            {
                                UpdateTarget(cues[j]);
                            }
                        }
                    }
                }
            }
        }
Beispiel #19
0
        /// <summary>
        /// Renders the currently applied settings to a MIDI file (in memory) and then sets up a player
        /// </summary>
        bool setupPlayerForPlayback()
        {
            if (drv != null)
            {
                drv.Dispose();
            }
            if (player != null)
            {
                player.Dispose();
            }
            if (syn != null)
            {
                syn.Dispose();
            }
            var soundfontFile = @"Soundbanks\ExtractedSoundbank_" + (NLSTChoice + 10).ToString("X2") + ".dls";

            if (!File.Exists(soundfontFile))
            {
                MessageBox.Show("A required soundbank file is missing - Please see the Github on how to extract soundbanks from your ROM.");
                return(false);
            }
            var settings = new Settings();

            settings[ConfigurationKeys.SynthAudioChannels].IntValue = 2;
            syn      = new Synth(settings);
            syn.Gain = 0.5f;
            syn.LoadSoundFont(soundfontFile, true);
            for (int i = 0; i < 16; i++)
            {
                syn.SoundFontSelect(i, 1);
            }

            player = new Player(syn);
            var mid = exportMIDBytes();

            player.AddMem(mid, 0, mid.Length);
            drv = new AudioDriver(settings, syn);
            return(true);
        }
Beispiel #20
0
        public static void test()
        {
            var             candidates = GetQuietParts();
            List <CueRange> ranges2    = new List <CueRange>();

            candidates.ForEach((queData) => {
                var ms = AudioDriver.TickSpanToMs(SongDataHolder.I.songData, queData.fromTick, queData.toTick);
                if (ms >= 2000f)
                {
                    ranges2.Add(queData);
                }
            });

            // MelonLogger.Log($"Found {ranges2.Count} candidates!");
            Data.darts = new int[ranges2.Count][];

            int i = 0;

            foreach (CueRange Cue in ranges2)
            {
                Data.darts[i] = new int[] { Cue.enableFrom, Cue.enableTo };
                i++;
            }
        }
Beispiel #21
0
 public void CreateAudioDriver()
 {
     using (var syn = new Synth(NewAlsaSettings()))
         using (var audio = new AudioDriver(syn.Settings, syn))
             TextWriter.Null.WriteLine();
 }
Beispiel #22
0
 private static void Postfix(AudioDriver __instance)
 {
     AudicaMod.GetCues();
 }
Beispiel #23
0
        public AudioEngine(string audioDevice = null, AudioDriver driver = AudioDriver.Default, int sampleRate = 44100, int bufferLength = 1024, ResampleQuality resampleQuality = ResampleQuality.High)
        {
            this.resampleQuality = resampleQuality;

            SDL.SDL_InitSubSystem(SDL.SDL_INIT_AUDIO);

            audioCallback = AudioCallback;
            SDL.SDL_AudioSpec desired = new SDL.SDL_AudioSpec()
            {
                freq     = sampleRate,
                format   = SDL.AUDIO_S16,
                channels = 2,
                samples  = (ushort)bufferLength,
                callback = audioCallback,
            };

            if (driver == AudioDriver.File)
            {
                audioSpec   = desired;
                audioDriver = driver;
            }
            else
            {
                string[] audioDrivers = new string[SDL.SDL_GetNumAudioDrivers()];
                for (int i = 0; i < audioDrivers.Length; i++)
                {
                    audioDrivers[i] = SDL.SDL_GetAudioDriver(i);
                }

                string driverName         = audioDrivers[0];
                string driverFallbackName = audioDrivers.Length > 1 ? audioDrivers[1] : null;

                if (driver != AudioDriver.Default)
                {
                    driverName = driver.ToString();
                }

                int init = SDL.SDL_AudioInit(driverName.ToLower());
                if (init != 0 && driverName == AudioDriver.XAudio2.ToString().ToLower() && driverFallbackName != null)
                {
                    // supplied SDL.dll does not support XAudio2, fallback to next driver
                    driverName = driverFallbackName;
                    init       = SDL.SDL_AudioInit(driverName.ToLower());
                }
                if (init != 0)
                {
                    throw new ApplicationException("Failed to initialize audio driver " + driverName + ": " + SDL.SDL_GetError());
                }

                Enum.TryParse(driverName, true, out audioDriver);

                if (audioDevice == null)
                {
                    string[] audioDevices = new string[SDL.SDL_GetNumAudioDevices(0)];
                    for (int i = 0; i < audioDevices.Length; i++)
                    {
                        audioDevices[i] = SDL.SDL_GetAudioDeviceName(i, 0);
                    }

                    audioDevice = audioDevices.Length > 0 ? audioDevices[0] : null;
                }

                outputDevice = SDL.SDL_OpenAudioDevice(audioDevice, 0, ref desired, out audioSpec, 0);
                if (outputDevice == 0)
                {
                    throw new ApplicationException("Failed to open audio device " + audioDevice + ": " + SDL.SDL_GetError());
                }
            }

            if (audioSpec.format == SDL.AUDIO_S32)
            {
                bytesPerSample = (uint)audioSpec.channels * 4;
            }
            else if (audioSpec.format == SDL.AUDIO_S16 || audioSpec.format == SDL.AUDIO_F32)
            {
                bytesPerSample = (uint)audioSpec.channels * 2;
            }
            else if (audioSpec.format == SDL.AUDIO_S8 || audioSpec.format == SDL.AUDIO_U8)
            {
                bytesPerSample = (uint)audioSpec.channels * 1;
            }

            if (audioSpec.size == 0)
            {
                audioSpec.size = audioSpec.samples * bytesPerSample;
            }

            emptyBuffer = new byte[audioSpec.size];
            audioBuffer = new byte[audioSpec.size];

            lastCallback = 0.0;
            bufferTimer  = Stopwatch.StartNew();

            if (outputDevice != 0)
            {
                SDL.SDL_PauseAudioDevice(outputDevice, 0);
            }
        }
Beispiel #24
0
 private static void Postfix(AudioDriver __instance)
 {
     ModStatusHandler.ShowEnabledString();
     MelonCoroutines.Start(ModifierManager.ProcessQueueDelayed());
 }
 public void CreateAudioDriver()
 {
     using (var syn = new Synth (NewAlsaSettings ()))
         using (var audio = new AudioDriver (syn.Settings, syn))
             TextWriter.Null.WriteLine ();
 }
Beispiel #26
0
        /// <summary>
        /// 음악 출력 장치와 타이머를 초기화하고, 현재 음악 테마를 SFXThemeName으로 설정합니다.
        /// </summary>
        /// <param name="SFXThemeName">설정할 음악 테마 이름</param>
        /// <param name="noteResolution">단위 리듬</param>
        /// <param name="timerTickDelegates">타이머의 틱마다 추가로 실행할 메서드의 대리자 목록</param>
        public static void Initialize(string SFXThemeName, int noteResolution, Timer.TickDelegate[] timerTickDelegates = null)
        {
            IsReady  = false;
            HasStart = false;

            //outDevice = new OutputDevice(0);
            settings = new Settings();
            settings[ConfigurationKeys.SynthAudioChannels].IntValue = 2;

            syn = new Synth(settings);
            try
            {
                syn.LoadSoundFont("FluidR3_GM.sf2", true);
            }
            catch (FileNotFoundException e)
            {
                Console.WriteLine(e.StackTrace);
                return;
            }
            for (int i = 0; i < 16; i++)
            {
                syn.SoundFontSelect(i, 0);
            }

            adriver = new AudioDriver(syn.Settings, syn);

            /*
             * WaveInEvent recorder = new WaveInEvent
             * {
             *  WaveFormat = new WaveFormat(44100, 2)
             * };
             * BufferedWaveProvider sound = new BufferedWaveProvider(recorder.WaveFormat);
             * recorder.DataAvailable += (object sender, WaveInEventArgs e) =>
             * {
             *  sound.AddSamples(e.Buffer, 0, e.BytesRecorded);
             * };
             * recorder.StartRecording();
             * //sound.Read();
             */
            //playback = new WaveOutEvent();
            //playback.Init(sound);
            //playback.Play();

            /*
             * sound = new BufferedWaveProvider(new WaveFormat(44100, 2));
             * buffer = new byte[44100 * 4];
             * stream = new MemoryStream();
             * Task.Run(() => {
             *  soundStream = new RawSourceWaveStream(stream, new WaveFormat(44100, 2));
             *  reverb = new DmoEffectWaveProvider<DmoWavesReverb, DmoWavesReverb.Params>(soundStream);
             *  outputDevice = new WasapiOut();
             *
             *  outputDevice.Init(reverb);
             *  outputDevice.Play();
             * });
             */
            SFXTheme.CurrentSFXTheme = SFXTheme.FindSFXTheme(SFXThemeName);
            //Console.WriteLine(SFXThemeName + " " + SFXTheme.CurrentSFXTheme.Name);
            NoteResolution = noteResolution;
            Accompaniment.Initialize();

            tickDelegate += Tick;
            if (timerTickDelegates != null)
            {
                foreach (Timer.TickDelegate t in timerTickDelegates)
                {
                    tickDelegate += t;
                }
            }

            mgmt = new PowerManagement();
            mgmt.InitPowerEvents();
            mgmt.OnPowerSuspend += Suspend;
            mgmt.OnPowerResume  += Resume;

            syncPlayBuffer       = new List <Note>();
            syncTransitionBuffer = false;
            playPitchEventBuffer = new List <int>();

            accompanimentTickNumber = new Dictionary <int, int>();
            accompanimentPlayNumber = new Dictionary <int, int>();
            accompanimentTickNumber.Add(7, 0);
            accompanimentTickNumber.Add(8, 0);
            accompanimentPlayNumber.Add(7, 0);
            accompanimentPlayNumber.Add(8, 0);

            IsReady = true;

            /*
             * Task.Run(() =>
             * {
             *  var capture = new WasapiLoopbackCapture();
             *  var effectProvider = new DmoEffectWaveProvider<DmoWavesReverb, DmoWavesReverb.Params>(new WaveInProvider(capture));
             *
             *  PlayEffectorSound(capture, effectProvider);
             *  using (var outputDevice = new WasapiOut())
             *  {
             *      outputDevice.Init(effectProvider);
             *      capture.StartRecording();
             *      outputDevice.Play();
             *      while (capture.CaptureState != NAudio.CoreAudioApi.CaptureState.Stopped)
             *      {
             *          Thread.Sleep(500);
             *          if (!IsReady) capture.StopRecording();
             *      }
             *      Console.WriteLine("Effector stopped");
             *  }
             *
             * });
             */
        }
Beispiel #27
0
        public async Task Play()
        {
            using (var settings = new Settings()) {
                settings[ConfigurationKeys.AudioDriver].StringValue     = "pulseaudio";
                settings[ConfigurationKeys.SynthAudioChannels].IntValue = 2;
                settings[ConfigurationKeys.AudioRealtimePrio].IntValue  = 0;
                settings[ConfigurationKeys.SynthVerbose].IntValue       = 0;
                settings[ConfigurationKeys.AudioPeriodSize].IntValue    = 1024;
                settings[ConfigurationKeys.SynthReverbActive].IntValue  = ReverbEnabled ? 1 : 0;
                settings[ConfigurationKeys.SynthChorusActive].IntValue  = ChorusEnabled ? 1 : 0;

                using (var syn = new Synth(settings)) {
                    syn.LoadSoundFont("/usr/share/sounds/sf2/FluidR3_GM.sf2", true);

                    using (var adriver = new AudioDriver(syn.Settings, syn)) {
                        if (ReverbEnabled)
                        {
                            syn.SetReverb(ReverbRoomSize, ReverbDamping, ReverbWidth, ReverbLevel);
                        }

                        if (ChorusEnabled)
                        {
                            syn.SetChorus(ChorusNumVoices, ChorusLevel, ChorusSpeed, ChorusDepthMS, ChorusMod);
                        }

                        // Hardcoded, will be changed in the future
                        syn.ProgramChange(1, (int)GeneralMidi.Violin);

                        if (Midi != null)
                        {
                            // Meanwhile we are cheating a little bit and using the build-in FluidSynth MIDI player
                            // In the future it would be nice to read the events from the MIDI file and play them ourselves
                            // That way we could mix MIDI files with our own music expressions, and maybe even transform MIDI files
                            using (var player = new Player(syn)) {
                                player.Add(Midi);
                                player.Play();
                                // Synchronous join. Thankfully this code runs in a separate thread. Would be nice to have a async version
                                player.Join();
                            }
                        }
                        else
                        {
                            var sw = new System.Diagnostics.Stopwatch();
                            sw.Start();

                            // Explaining the timer process used
                            // All note commands (NoteOn/NoteOff) are ordered by their noteCommand.Timestamp (value in milliseconds relative to the beginning
                            // of the music). Let's imagine that two of them are, for example, one second apart. Ideally, we would want to do a
                            // Thread.Sleep( 1000 ) and hope that the thread would unblock exactly 1000 milliseconds after. However, the system's clock
                            // resolution varies between different Operating Systems and different physical Processors. So we could not be sure when the thread
                            // would be woken. Instead we do a Thread.Sleep( 1000 - minResolution ), which hopefully means that our thread will awake before the note is due
                            // Three things can happen now:
                            //   - We can be early, i.e. we still have to wait a bit, and for that we use a simple loop with a high-precision Stopwatch
                            //   - We can be on time, in which case we just play the note
                            //   - We can be late, in wich case we play the note right away and add to the drift variable how late we are
                            // Every subsequent timestamp will have the drift variable added to it to compensate

                            int minResolution = 30;
                            int drift         = 0;

                            foreach (INoteCommand noteCommand in BuildCommands(Notes))
                            {
                                int timestamp = noteCommand.Timestamp + drift;

                                int elapsed = (int)sw.Elapsed.TotalMilliseconds;

                                if (timestamp - minResolution > elapsed)
                                {
                                    await Task.Delay(Math.Max(0, timestamp - elapsed - minResolution));
                                }

                                while (timestamp > sw.Elapsed.TotalMilliseconds)
                                {
                                    Thread.Sleep(0);
                                }

                                elapsed = (int)sw.Elapsed.TotalMilliseconds;

                                noteCommand.Apply(syn);

                                if (timestamp < elapsed)
                                {
                                    drift += elapsed - timestamp;
                                }
                            }

                            sw.Stop();

                            Console.WriteLine($"Total drift: {drift}ms out of {sw.Elapsed.TotalMilliseconds}ms");
                        }
                    }
                }
            }
        }