New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Playback produces unexpected results in Unity #31
Comments
Hi, Please give me a MIDI file you see issue on. I'll test playback on my side. |
Here are a couple, one simple and one complex. |
I don't see problems with But I confirm the problem with some notes aren't played in the complex file. Thank you a lot for reporting the issue! I'll fix it as soon as possible. |
Can you send me your script that you used to play these? |
I use this simple program: using Melanchall.DryWetMidi.Devices;
using Melanchall.DryWetMidi.Smf;
namespace Issue31
{
class Program
{
static void Main(string[] args)
{
var outputDevice = OutputDevice.GetByName("Microsoft GS Wavetable Synth");
Console.WriteLine("Press any key to start 'Never Look Back.mid' playback...");
Console.ReadKey();
var midiFile = MidiFile.Read("Never Look Back.mid");
var playback = midiFile.GetPlayback(outputDevice);
playback.Start();
Console.WriteLine("Press any key to stop playback...");
Console.ReadKey();
playback.Stop();
Console.WriteLine("Press any key to start 'percussion test.mid' playback...");
Console.ReadKey();
midiFile = MidiFile.Read("percussion test.mid");
playback = midiFile.GetPlayback(outputDevice);
playback.Start();
Console.WriteLine("Press any key to stop playback...");
Console.ReadKey();
playback.Stop();
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
} |
I've fixed bug with some notes aren't played. Please get latest sources from develop branch. |
Okay, getting somewhere. I opened up a new project and ran parts of that code you provided. The file now plays for a few seconds before this is thrown:
If I try to hit play again, nothing plays and I have to restart Unity for it to play again. |
I don't see any issues on my side. I suppose it's related to Unity and how it works. I'm testing in Visual Studio with .NET (not Mono used by Unity). So let's investigate what can be wrong.
The line where you get the exception is: public bool IsRunning => _stopwatch.IsRunning; So the only thing that can give NRE is |
Getting closer. The thread was indeed closing a short while after starting the playback. I've fixed that so the thread does not stop. But, stopping and running the program again produces no sound. However, I also brought the exact code you provided into a new, separate .NET solution and it seems to work fine, except that any note being played when stopping playback is continued to be held, even after starting the next MIDI file. |
Do you mean stopping playback or program? Can you provide code you're performing tests with?
It's OK. To stop currently playing notes on |
I mean stopping the program, then rerunning the program. Here's my code (omitting extraneous animation code): /* other private vars */
private Playback _playback;
private void Start()
{
/* Get MIDI file and copy to assets folder */
var midiFilePath = EditorUtility.OpenFilePanel("Open MIDI file", "%USERPROFILE%/Desktop", "mid");
File.Delete(Application.dataPath + @"/Scripts/in.mid");
File.Copy(midiFilePath, Application.dataPath + @"/Scripts/in.mid");
var midiFile = MidiFile.Read(Application.dataPath + @"/Scripts/in.mid");
Global.CurrentTempoMap = midiFile.GetTempoMap();
var outputDevice = OutputDevice.GetByName("Microsoft GS Wavetable Synth");
_playback = midiFile.GetPlayback(outputDevice);
/* ui and note handling */
// start coroutine
StartCoroutine(StartMusicAndAnimation(/*extraneous arguments*/));
}
private IEnumerator StartMusicAndAnimation( /*extraneous arguments*/ )
{
yield return null;
// Accomodate for panel open
Global.SettleTime += Time.unscaledTime;
var played = false;
while (!played)
{
if (Time.unscaledTime >= Global.SettleTime - 0.1)
{
// Begin instrument and music playback
_playback.Start();
/* animation handling */
played = true;
}
yield return null;
}
// Prevent the thread from closing
while (true)
{
yield return null;
}
} |
Instead of
So put output device to field as you do with playback and try to change your last-loop part to: while (_playback.IsRunning)
{
yield return null;
}
_playback.Dispose();
_outputDevice.Dispose(); |
Okay, I've added the dispose methods to the end of the script just like you provided. Now Unity won't even play the second time, Unity itself just freezes. This may be some bug in Unity or something. I'm gonna poke around with the settings and see if I can get something to work. |
OK, please let me know if you solve the problem. |
@wyskoj Any news? |
I'd like to say I have. I tried changing a bunch of stuff trying to get it to work. Nothing worked so I'm taking a hiatus... I'll get back to it a different day. |
I'm also experiencing the same issue whereas playback.IsRunning() returns true, although no sound is playing. The same applies to the midi files being played for a few seconds then cut short as the thread closes. Let me know when you have a solution, I saw that you managed to open the playback on a separate thread, however it won't work when restarting. |
DWM Test2.zip
Let me know if you have issues or questions. |
Thank you a lot! Please say exact version of Unity you use so I can be in the same conditions as you. |
Unity 2019.1.0f2 |
@wyskoj I confirm the issue. I've spent several hours to find the root of the behavior, but I found nothing. Seems like there is a deadlock inside of Unity/Mono on objects cleanup. I've tried to follow this article and removed all finalizers but it didn't solve the problem. Do you mind if I create thread on Unity forum or create a support request attaching provided Unity project? |
Please do! Thanks for all your help. |
Answer from Unity tech support:
|
Answer from Unity tech support:
I'll determine if winmm thread can be terminated on dispose. Probably I hold some references that must be destroyed. |
Problem with using winmm timers in Unity/Mono. Unity team is working on a solution. Waiting for news from them. |
Unity tech support:
It seems the bug will not be fixed in nearest future :( |
I'll take it for granted that there is no current workaround regarding this issue? |
@Teafuu Right now there are no workarounds. BUT I'm going to implement kind of ticking mode for playback/clock to choose between:
I'll implement this API as soon as possible. |
I've added By default RegularPrecisionTickGenerator All you need for your project is to get playback with this code: _playback = _midiFile.GetPlayback(_outputDevice, new MidiClockSettings
{
CreateTickGeneratorCallback = interval => new RegularPrecisionTickGenerator(interval)
});
Manual ticking Playback creation: _playback = _midiFile.GetPlayback(_outputDevice, new MidiClockSettings
{
CreateTickGeneratorCallback = interval => null
}); In coroutine: _playback.Start();
while (_playback.IsRunning)
{
_playback.TickClock();
yield return null;
} I suppose the second approach will be the most accurate. Both ways work perfectly in Unity project. You can even create your own tick generator and use it. @wyskoj @Teafuu Please download sources of the library from develop branch and replace all your DryWetMidi folder since there are a lot of changes in the library structure (nearest release will be major one including breaking changes). Plaese confirm the issue is resolved so I can close it. |
I have tried both of the methods you have provided, but each drops or sticks on a considerable amount of notes. But I assume this is just a limitation of lower fidelity timers. It also seems to not playback the first second or so of the MIDI file. |
Let's create custom tick generator that will be super accurate (but it consumes a lot of CPU so it's not recommended to implement timers in this way): private sealed class LoopTickGenerator : ITickGenerator
{
public event EventHandler TickGenerated;
private readonly Thread _thread;
private bool _disposed;
public LoopTickGenerator()
{
_thread = new Thread(() =>
{
for (var i = 0; ; i++)
{
if (i % 1000 == 0)
TickGenerated?.Invoke(this, EventArgs.Empty);
}
});
}
public void TryStart()
{
if (_thread.IsAlive)
return;
_thread.Start();
}
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
_thread.Abort();
}
_disposed = true;
}
} So playback should be created like this: _playback = _midiFile.GetPlayback(_outputDevice, new MidiClockSettings
{
CreateTickGeneratorCallback = interval => new LoopTickGenerator()
}); And remove manual ticking ( @wyskoj Please try this approach just for test. While you're trying it, I'll investigate current problems. But at now it seems they are indeed related with low accuracy (in case of |
I've checked the following approaches on Never Look Back.mid file:
All events are went through output device, I've dumped delays for all events for all approaches to files: CheckFilePlayback_RegularPrecisionTickGenerator.txt Files contain strings like this:
It means:
I don't see any catastrophic delays. Maybe output device is unable to process some events in time due to a lot of events are incoming at almost same time. I'll continue investigation of possible issues. |
oh,met this issue and finally find this page. hoping solving soon. |
@hoszeching I've contacted support recently and unfortunately the priority of the issue is very low so it seems it will not be fixed in near future. |
I'll be waiting patiently, been putting it on hold until it gets fixed 👍 |
I'm taking a crack at this again... using System;
using System.Collections;
using System.Collections.Generic;
using Melanchall.DryWetMidi.Core;
using Melanchall.DryWetMidi.Devices;
using UnityEngine;
public class DWMHandle : MonoBehaviour
{
private Playback _playback;
private OutputDevice _outputDevice;
// Start is called before the first frame update
void Start() {
var midiFile = MidiFile.Read("Assets/GROOVE.MID");
_outputDevice = OutputDevice.GetByName("Microsoft GS Wavetable Synth");
_playback = midiFile.GetPlayback(_outputDevice, new MidiClockSettings
{
CreateTickGeneratorCallback = interval => new RegularPrecisionTickGenerator(interval)
});
_playback.InterruptNotesOnStop = true;
StartCoroutine(StartMusic());
}
private IEnumerator StartMusic() {
_playback.Start();
while (_playback.IsRunning) {
yield return null;
_playback.TickClock();
}
_playback.Dispose();
}
private void OnApplicationQuit() {
_playback.Stop();
_playback.Dispose();
}
} No crashes, so far.... and notes don't seem to drop or have any inconsistencies. Not sure what's different except unity version = 2018.4.15f1. Could possibly be a bug sometime after this Unity release? |
Hm, really strange that this approach didn't work for you when I wrote about it last time and now it works :) Maybe it's because of combination of @Teafuu Please try approach shown by @wyskoj. Does it work for you? |
It seems the bug will not be fixed :( I've contacted Unity tech support again: Me
Unity
|
Miracle happened, the bug is fixed now! Message from Unity tech support:
So I'm finally closing the issue. (more than 2 years, heh) |
Awesome!! |
I've integrated DWM into my Unity project (2019.1.0f2) for MIDI file playback and parsing. I've called
playback.Start()
and the MIDI file will not play. I have tried several MIDI files and nothing will play.Later, in a coroutine (so the MIDI file starts playing at a specific time):
Playback doesn't want to start, even on the main thread.
Also,
playback.Play()
works, but it freezes the thread which I don't want, and it also drops a lot of notes, even on really simple MIDI files.No exceptions are thrown.
What am I missing? And is there some fix to the dropped notes?
The text was updated successfully, but these errors were encountered: