|
Dave,
Just an FYI... When closing the app, it tends to hang on the "InternalReset()" when comparing "NativeMethods.midiInReset(handle)". It never exits unless I bypass the if statement completely. This is the first I've had time to play with the latest code.
|
|
|
|
|
Can you navigate to private void Dispose(bool disposing) in MidiInput and put a breakpoint on if (buffers.Count != 0) (line 351). Does it get hit, and if so, what is the value of buffers.Count ?
|
|
|
|
|
Yes, it gets hit, and the buffer count is 7.
|
|
|
|
|
The issue is actually caused by line 454. If I remark 454, and the brackets associated with that if statement, all works as expected. I also verified it to hang on the actual statement NativeMethods.midiInReset(handle) by inserting it above line 454 in a console.write command.
Not sure if it matters, but I'm testing this with Maple Virtual Midi Cable. Perhaps its a problem with that. I don't have this on my other machine that I'm using the controller on, and haven't had time to verify it on that machine.
|
|
|
|
|
I can't test with Maple Virtual Midi Cable as it won't run on Win 7 x64. I have to suspect that there is a problem with their driver as it works fine with all my MidiMan and MAudio hardware, as well as with MidiYoke.
There seems to be something a little odd. There are 8 buffers assigned when Record is called. Every time one is used, another is assigned to take it's place unless the input is closing or resetting, so the first time line 454 is hit, there should be 8 buffers, even if you have received data.
Perhaps there is something going wrong for you in Callback ? The buffers are returned to there which will reduce the count to 7 when the first SysEx is received, but should be re-added in the block from line 280. Can you put breakpoints on:
- Line 253 and check the value of
wMsg it should be 964. This should get hit 9 times - the first time SysEx is received, then once for each of the 8 buffers when midiInReset is called.
- Line 276: Does it get hit? I'm suspecting not from what you describe. If not, the call to
midiInUnprepareHeader on line 274 may be failing. If so, refactor that line so you can capture the result and let me know what the value is.
- Line 288: Does it get hit? If the above succeeded then it's possible that
midiInPrepareHeader or midiInAddBuffer are failing on lines 285 and 286. If so, refactor and call the two functions separately and let me know the return result(s).
It's very important that midiInReset is called so that the buffers can be returned by Windows, unprepared and freed - all this while the handle is still valid. Bypassing it may get the program to the end but could eventually cause RAM access problems without a reboot, BSOD or just an entire system hang/freeze.
I did consider creating a custom MidiException class and throwing an instance when a function returned non-zero. I decided against it in the end as it's important that everything gets disposed while live and not during a later GC. Halting on an exception would have made that impossible.
If we can't get to the bottom of this I will add some logging so the error codes are captured and saved.
|
|
|
|
|
Ah!
I don't run Maple on my win7 x64 system because it has a controller, but am running it on the XP system because it doesn't, and I needed some way to test.
Is line 253 = "MidiBuffer midiBuffer = null;" Buffers = 8. from there, it makes it to line 274 and stalls as stated previously. It does not hit 276 or 288. It simply stalls dead.
Dave,
I do appriciate the effort you've put into this, but as with the PureMIDI example, this has become much too complex for my needs, and has way too much overhead. I can connect to ports without issue, I can send data to the controller, now I simply need to capture the raw data from the controller. I don't even want to split the status byte into type and channel. I would like it as 0xD2D1st. Anything and everything beyond that is complete overkill for my needs. At most providing this as a byte array would be acceptable as well.
For my application, I will be capturing the control input, and placing into a map file in the format 0xst:0xD1:0xD2. I will likely be looping that back to the controller with possible D2 and status mods to light the corresponding Button lamp. That is basically it. I found that some controllers like the M-Audio xponent, will send a proper note on and note off status with a velocity of 0x40, while others will always send a note on with a velocity of 0x00 or 0x7f to indicate note on or off. I need to be able to work with both these possabilities while mapping the controllers.
I can send a sysex command if necesary as well to change controller modes. As for reading the sysex feedback, I may or may not chase that at some point, but not sure it's necessary.
I'm glad you're putting together an article for this, as I do think it needs better attention, but I also feel that it's going to be too advanced for most judging by the current code. For a novice, I would think providing a basic example would suffice, and then maybe expand on that with a couple more articles for the seasoned, and then advanced users to learn from.
What I need, is to learn how to make the callback, to collect the Midi Input.
Again, Thanks, but as it stands, I can't use much of this.
Jeff
|
|
|
|
|
OK, as simple as I can make it. MIDI input (without SysEx), no error checking:
using System;
using System.Runtime.InteropServices;
class Program
{
private static MidiTest midiTest;
static void Main(string[] args)
{
midiTest = new MidiTest();
midiTest.Start();
Console.ReadKey();
midiTest.Stop();
}
}
internal class MidiTest
{
private delegate void MidiInProc(IntPtr hMidiIn, int wMsg, IntPtr dwInstance, IntPtr dwParam1, IntPtr dwParam2);
private IntPtr handle;
private MidiInProc callback;
public void Start()
{
callback = new MidiInProc(Callback);
midiInOpen(out handle, 0, callback, IntPtr.Zero, 0x00030000);
midiInStart(handle);
}
public void Stop()
{
midiInStop(handle);
midiInClose(handle);
handle = IntPtr.Zero;
callback = null;
}
private void Callback(IntPtr hMidiIn, int wMsg, IntPtr dwInstance, IntPtr dwParam1, IntPtr dwParam2)
{
switch (wMsg)
{
case 0x3C3:
int data = (int)dwParam1;
int timestamp = (int)dwParam2;
break;
}
}
[DllImport("Winmm.dll")]
private static extern int midiInOpen(out IntPtr lphMidiIn, int uDeviceID, MidiInProc dwCallback, IntPtr dwCallbackInstance, int dwFlags);
[DllImport("Winmm.dll")]
private static extern int midiInStart(IntPtr hMidiIn);
[DllImport("Winmm.dll")]
private static extern int midiInStop(IntPtr hMidiIn);
[DllImport("Winmm.dll")]
private static extern int midiInClose(IntPtr hMidiIn);
}
|
|
|
|
|
Just what I was looking for Dave!
Thanks much!
|
|
|
|
|
One quick question, are you terminating the application by pressing a key or by the close button on the console window. If the latter, that could be why it's hanging.
Edit: Some research reveals this quote from MSDN:
"To stop recording, use midiInStop. Before closing the device by using the midiInClose function, mark any pending data blocks as being done by calling midiInReset".
So it appears that the procedure should be Stop, Reset, Unprepare and free the buffers, then Close. I haven't used the Stop as it appeared (possibly erroneously) that Reset did the same thing, and easy fix thankfully.
modified 11-Oct-12 17:54pm.
|
|
|
|
|
Well, in the begining, the first time I tried your first example, I closed it via the close button of the console, and quickly learned a leason. When I get this error now, it is only with Maple VMC. In my WPF app, I have a onclose function that closes the midi ports. I don't know of a way to do this with a console window however, and as it's just for simple testing, I'm not too worried about that... Just have to remember to close it properly with any keypress.
I've got to reboot now, as I tried your simple Midi, and, guess what. I did it again! Not I've got an active service and it won't run because it's locked. lol.
I'm guessing that's just what I needed. Thanks once again!
Jeff
|
|
|
|
|
BTW, I removed Maple VMC, and installed Midi-Yoke, and problem above appears resolved. I suspec you are right about Maples driver. I've heard others having problems with Maple also, so I'll put that down as accurate.
|
|
|
|
|
Thanks, good to know
|
|
|
|
|
I've done some more investigation into this and have found others that have had this problem with different hardware.
The problem seems to be that some drivers block on the Reset call until all the MIDIHDR s are returned. This means that UnprepareHeader cannot be called on any buffer (during a reset only) until they have all been returned to windows, so can't be done in the callback!
I have managed to code around this by setting an IsResetting flag when Reset is called, having an IsReturned flag on each buffer that is set it is returned and when the last buffer is returned - launch a thread (so the callback returns!) that unprepares and frees the buffers. A bit of a pain but it appears to work and should do on all systems
|
|
|
|
|
That's good to know Dave!
I've been playing more with the Pure MIDI code, and determined his exit code also failed on closing the MIDI output. It's not closing the buffers either. Between your code and the Pure MIDI code, I've gotten the Short MIDI in figured out, and the outgoing Long & Short msgs figured out.
I thank you for all your efforts once again!
|
|
|
|
|
Got another query here...
When a SYSEX is sent out over MIDI, Should it not trigger a MIM_LONGDATA message in my app regardless of if I have code to do something with that message or not?
Or, Is there something I need to do to setup my callback to see these messages?
|
|
|
|
|
MIM_LONGDATA will be sent to your callback only if (some of these are obvious so I'm sure you can ignore them but I'll include them for completeness!):
- The MIDI Input is open (midiInOpen)
- The MIDI Input is started (midiInStart)
- There is at least one buffer available
- The buffer is prepared (midiInPrepareHeader)
- The buffer has been added (midiInAddBuffer)
- The buffer has been (re)initialized correctly (dwFlags cleared etc if reusing the buffer)
What caught me out the first time I ever tried it was that I hadn't done 4 and 5. I expected a buffer to just be given to me that the system created for the SysEx. Unfortunately, it will only fill buffers we create and add.
|
|
|
|
|
Interesting!
I assumed that because the port was open, that the message would be toggled regardless in the callback, and if it wasn't handled, it would then be ignored. That would explain why I don't see that occurring at all then. I need to create a buffer, prepare it, add it, and be sure I have the right flags then before it is even visible in the message cue.
Thanks again Dave!
|
|
|
|
|
Correct, and no problem.
dwBufferLength needs to be set to the size (in bytes) of the buffer.
lpData needs to be a valid memory location that unmanaged code can write to. I use Marshal.AllocHGlobal using the same value as dwBufferLength . Make sure you free this once it's returned with Marshal.FreeHGlobal .
dwBytesRecorded will tell you how many bytes have been received.
If the buffer isn't big enough, or more SysEx is received it will use the next buffer so you need to ensure you have at least two available.
You only need to clear the flags if you are reusing the buffer once you are done with a used one. Reusing can give better performance as there is no need to be constantly allocating and freeing memory. I just add new new ones and discard the old personally.
|
|
|
|
|
In a C# 2008 console application, I am getting the following error:
"Error 4 The type or namespace name 'eDataContext' could not be found (are you missing a using directive or an assembly reference?)".
I am getting this error on the following line of code:
eDataContext rptData = new eDataContext();
I have created the *.dbml file, I have dragged a table onto the designer surface, I have added the *.dbml file to the project folder that will be using the *.dbml file, and I have built the application.
The name 'eDataContext', in the name of the datacontext object that is located in the eD.designer.cs file.
The only thing that i can think to try is to add something in an app.config file, but I do not know what to add.
Thus can you tell me and/or point me to a reference on what i can do to solve this problem?
|
|
|
|
|
You must have missed importing the namespace. Find out what namespace eDataContext belongs to and import it in your code. For example, if eDataContext belongs to dcof.Entities namespace, you must write this line of code in the .cs file where you use the eDataContext class.
using dcof.Entities;
|
|
|
|
|
I have a piece of software that compiles and installs properly. It uses loosely coupled events to pass information to another application. All of the code works except for the other application actually receiving the event and processing it. It works on Windows XP perfectly but not on Windows 7.
Thanks
|
|
|
|
|
Without seeing any code, we can only offer guesses at best. It could be security related.
|
|
|
|
|
Due to company policy I can't post the code. If it were security related what would your best guess be. I have turned off UAC and ajusted permissions on many registry entries just to get it installed.
Thanks,
|
|
|
|
|
Well, for a start, I'd be using remote debugging on a debug version of the app installed on that machine and I'd check to see whether exceptions were being consumed. Also, is your LCE a COM event? If so, that can sometimes be a problem and you may have to adjust DCOM settings.
|
|
|
|
|
It is indeed a com event. I will have a look at DCOM settings. Thank you. If you have any specific suggestions let me know. Also if you can point me to a good remote debugging tutorial I would appreciate it. I am actually an Electrical Engineer with a a computer science degree and this is one of my first major coding projects and have never worked with com/com+/dcom before.
Thanks,
|
|
|
|
|