pointer to the first TList structure

This is the forum for miscellaneous technical/programming questions.

Moderator: 2ffat

pointer to the first TList structure

Postby mark_c » Thu Jul 02, 2020 6:18 am

Hello,
I'm not sure if what I wrote is correct. I am using a TList as a container for pointers to MIDIEVENT structures and after filling the TList, I have to initialize the member (header.lpData) of the MIDIHDR structure with the start address of the first structure present in the TList.

thank you

Code: Select all
        TList *MyList = new TList;
        MIDIEVENT *ev = NULL;
        MIDIHDR header;
      
        ev = new MIDIEVENT;
        ev->dwDeltaTime = 00;
        ev->dwStreamID = 0;
        ev->dwEvent = 0x00403C90;
        MyList->Add(ev);

        ev = new MIDIEVENT;
        ev->dwDeltaTime = 100;
        ev->dwStreamID = 0;
        ev->dwEvent = 0x00403C80;
        MyList->Add(ev);

        MIDIEVENT *s = (MIDIEVENT *) MyList->Items[0];

        header.lpData = ( LPSTR )&s;
        header.dwBufferLength = header.dwBytesRecorded = sizeof( s );
        header.dwUser = 0;
        header.dwFlags = 0;
        header.dwOffset = 0;

       ...........
mark_c
BCBJ Master
BCBJ Master
 
Posts: 243
Joined: Thu Jun 21, 2012 1:13 am

Re: pointer to the first TList structure

Postby rlebeau » Thu Jul 02, 2020 11:35 am

mark_c wrote:I am using a TList as a container for pointers to MIDIEVENT structures


Why are you using a TList and not an array/vector instead?

mark_c wrote:after filling the TList, I have to initialize the member (header.lpData) of the MIDIHDR structure with the start address of the first structure present in the TList.


You are not setting the lpData to the address of the first MIDIEVENT struct. You are setting it to the address of a local variable that itself contains the address of the first MDIEVENT struct. You need to drop the '&' operator, eg:

Code: Select all
MIDIEVENT *s = (MIDIEVENT *) MyList->Items[0];
header.lpData = (LPSTR) s;


However, your code still won't work, because in order to pass around multiple MIDIEVENTs in a single MIDIHDR, the MIDIHDR requires an array of MIDIEVENT instances, not an array of pointers to MIDIEVENT instances. The dwBufferLength must be set to the size of the whole array, so MIDI can calculate how many MIDIEVENTs are in the array, eg:

Code: Select all
MIDIEVENT MyArr[2] = {};
MIDIHDR header = {};
      
MIDIEVENT *ev = &MyArr[0];
ev->dwDeltaTime = 00;
ev->dwStreamID = 0;
ev->dwEvent = 0x00403C90;

ev = &MyArr[1];
ev->dwDeltaTime = 100;
ev->dwStreamID = 0;
ev->dwEvent = 0x00403C80;

header.lpData = (LPSTR) MyArr;
header.dwBufferLength = header.dwBytesRecorded = sizeof(MyArr);
header.dwUser = 0;
header.dwFlags = 0;
header.dwOffset = 0;

...


Or, if you want to use a dynamically allocated array:

Code: Select all
int count = 2;
MIDIEVENT *MyArr = new MIDIEVENT[count];
MIDIHDR header = {};
      
MIDIEVENT *ev = &MyArr[0];
ev->dwDeltaTime = 00;
ev->dwStreamID = 0;
ev->dwEvent = 0x00403C90;

ev = &MyArr[1];
ev->dwDeltaTime = 100;
ev->dwStreamID = 0;
ev->dwEvent = 0x00403C80;

header.lpData = (LPSTR) MyArr;
header.dwBufferLength = header.dwBytesRecorded = (sizeof(MIDIEVENT) * count);
header.dwUser = 0;
header.dwFlags = 0;
header.dwOffset = 0;

...

delete[] MyArr;


Or better:

Code: Select all
#include <vector>

std::vector<MIDIEVENT> MyVec(2);
MIDIHDR header = {};
      
MIDIEVENT *ev = &MyVec[0];
ev->dwDeltaTime = 00;
ev->dwStreamID = 0;
ev->dwEvent = 0x00403C90;

ev = &MyVec[1];
ev->dwDeltaTime = 100;
ev->dwStreamID = 0;
ev->dwEvent = 0x00403C80;

header.lpData = (LPSTR) MyVec.data();
header.dwBufferLength = header.dwBytesRecorded = (sizeof(MIDIEVENT) * MyVec.size());
header.dwUser = 0;
header.dwFlags = 0;
header.dwOffset = 0;

...
Last edited by rlebeau on Mon Jul 06, 2020 1:00 pm, edited 1 time in total.
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1688
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: pointer to the first TList structure

Postby mark_c » Fri Jul 03, 2020 5:52 am

thanks Remy.
I used a TList to take advantage of the Sort in case I needed it.
mark_c
BCBJ Master
BCBJ Master
 
Posts: 243
Joined: Thu Jun 21, 2012 1:13 am

Re: pointer to the first TList structure

Postby mark_c » Mon Jul 06, 2020 2:28 am

I'm still studying these midi functions of windows but the darkest part of the code is in the calculation of the buffer to be passed to the appropriate function.

If I write:
Code: Select all
header.dwBufferLength = header.dwBytesRecorded = (sizeof (MIDIEVENT) * 2) - (sizeof (ev-> dwParms) * 2);

everything freezes.

if I leave it like this:
Code: Select all
header.dwBytesRecorded = (sizeof (MIDIEVENT) * 2);

error 68.
mark_c
BCBJ Master
BCBJ Master
 
Posts: 243
Joined: Thu Jun 21, 2012 1:13 am

Re: pointer to the first TList structure

Postby rlebeau » Mon Jul 06, 2020 1:00 pm

mark_c wrote:I used a TList to take advantage of the Sort in case I needed it.


You can sort an array/vector using the std::sort() function in the standard <algorithm> header.

mark_c wrote:I'm still studying these midi functions of windows but the darkest part of the code is in the calculation of the buffer to be passed to the appropriate function.


Please show the entire code you are having trouble with. Which MIDI function(s) are you actually using?

mark_c wrote:If I write:
Code: Select all
header.dwBufferLength = header.dwBytesRecorded = (sizeof (MIDIEVENT) * 2) - (sizeof (ev-> dwParms) * 2);

everything freezes.


Are you subtracting 8 bytes from the buffer size in an attempt to account for this comment in the MIDIEVENT documentation?

dwParms

If dwEvent specifies MEVT_F_LONG and the length of the buffer, this member contains parameters for the event. This parameter data must be padded with zeros so that an integral number of DWORD values are stored. For example, if the event data is five bytes long, three pad bytes must follow the data for a total of eight bytes. In this case, the low 24 bits of dwEvent would contain the value 5.

If dwEvent specifies MEVT_F_SHORT, do not use this member in the stream buffer.


If so, then don't allocate an array of MIDIEVENT instances directly, as the elements will include the dwParms field. Do something more like this instead:

Code: Select all
#include <vector>

const size_t elemSize = offsetof(MIDIEVENT, dwParms);

std::vector<BYTE> MyVec(elemSize * 2);
MIDIHDR header = {};
     
MIDIEVENT *ev = reinterpret_cast<MIDIEVENT*>(MyVec.data());
ev->dwDeltaTime = 00;
ev->dwStreamID = 0;
ev->dwEvent = 0x00403C90;

ev = reinterpret_cast<MIDIEVENT*>(reinterpret_cast<LPBYTE>(ev) + elemSize);
ev->dwDeltaTime = 100;
ev->dwStreamID = 0;
ev->dwEvent = 0x00403C80;

header.lpData = (LPSTR) MyVec.data();
header.dwBufferLength = header.dwBytesRecorded = MyVec.size();
header.dwUser = 0;
header.dwFlags = 0;
header.dwOffset = 0;

...


Alternatively:

Code: Select all
struct alignas(MIDIEVENT) MIDIEVENT_NOPARMS
{
    DWORD dwDeltaTime;
    DWORD dwStreamID;
    DWORD dwEvent;
    //DWORD dwParms[];
};

#include <vector>

std::vector<MIDIEVENT_NOPARMS> MyVec(2);
MIDIHDR header = {};
     
MIDIEVENT_NOPARMS *ev = &MyVec[0];
ev->dwDeltaTime = 00;
ev->dwStreamID = 0;
ev->dwEvent = 0x00403C90;

ev = &MyVec[1];
ev->dwDeltaTime = 100;
ev->dwStreamID = 0;
ev->dwEvent = 0x00403C80;

header.lpData = (LPSTR) MyVec.data();
header.dwBufferLength = header.dwBytesRecorded = (sizeof(MIDIEVENT_NOPARMS) * MyVec.size());
header.dwUser = 0;
header.dwFlags = 0;
header.dwOffset = 0;

...


mark_c wrote:if I leave it like this:
Code: Select all
header.dwBytesRecorded = (sizeof (MIDIEVENT) * 2);

error 68.


That error is MIDIERR_NODEVICE, which has nothing to do with MIDIHDR. What function is returning that error exactly?
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1688
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: pointer to the first TList structure

Postby mark_c » Mon Jul 06, 2020 1:39 pm

thanks Remy.
Here is my experiment.

Code: Select all
void __fastcall TForm1::Button1Click(TObject *Sender)
{
        int num = MyList->Count;

        MIDIEVENT *MyArr = new MIDIEVENT[num];
        MIDIHDR header = {};
        MIDIEVENT *ev = NULL;

        int a=0;
        for(int i = 0; i < num; i++)
        {
                MyStruct *s = (MyStruct*) MyList->Items[i];

                if(s->a >= 0x80 && s->a <= 0x9F ||
                   s->a >= 0xA0 && s->a <= 0xAF ||
                   s->a >= 0xB0 && s->a <= 0xBF ||
                   s->a >= 0xE0 && s->a <= 0xEF)
                {
                        ev = &MyArr[a++];
                        ev->dwDeltaTime = s->deltaTime;
                        ev->dwStreamID = 0;
                        ev->dwEvent = (s->a << 0) | (s->b << 8) | (s->c << 16);
                }
        }

        HMIDISTRM out;
        UINT device_id = 0;
        unsigned long err;
        MIDIPROPTIMEDIV prop;

        if ((event = CreateEvent(0, FALSE, FALSE, 0)))
        {
                err = 0;
                if (!(err = midiStreamOpen(&out, &device_id, 1, (DWORD)midiCallback, 0, CALLBACK_FUNCTION)))
                {
                        // Set the timebase. Here I use 96 PPQN
                        prop.cbStruct = sizeof(MIDIPROPTIMEDIV);
                        prop.dwTimeDiv = 96;
                        midiStreamProperty(out, (LPBYTE)&prop, MIDIPROP_SET|MIDIPROP_TIMEDIV);

                        header.lpData = (LPSTR) MyArr;
                        header.dwBufferLength = header.dwBytesRecorded = (sizeof(MIDIEVENT) * num);
                         //header.dwUser = 0;
                        header.dwFlags = 0;
                        //header.dwOffset = 0;

                        err = midiOutPrepareHeader( ( HMIDIOUT )out, &header, sizeof( MIDIHDR ) );
                        if(!err)
                        {
                                err = midiStreamOut( out, &header, sizeof( MIDIHDR ) );
                                if(!err)
                                {
                                        err = midiStreamRestart(out);

                                        if(!err)
                                        WaitForSingleObject(event, INFINITE);
                                }

                        }
                        // Unprepare the buffer and MIDIHDR
                        midiOutUnprepareHeader(out, &header, sizeof(MIDIHDR));

                        midiStreamClose(out);
                }
        }

        CloseHandle(event);

        delete[] MyArr;
}
mark_c
BCBJ Master
BCBJ Master
 
Posts: 243
Joined: Thu Jun 21, 2012 1:13 am

Re: pointer to the first TList structure

Postby rlebeau » Mon Jul 06, 2020 5:09 pm

mark_c wrote:Here is my experiment.


And, did you try the suggestions I gave you? Particularly the part about NOT allocating full MIDIEVENT structs in your array, since you are not using the dwParms field? The MIDIEVENT struct is variable-length, but you are not accounting for that. For example, try this:

Code: Select all
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    const size_t elemSize = offsetof(MIDIEVENT, dwParms);
    int num = MyList->Count;

    BYTE *MyArr = new BYTE[elemSize * num]; // or std::vector<BYTE>, or DynamicArray<BYTE>
    MIDIHDR header = {};

    int a = 0;
    for(int i = 0; i < num; ++i)
    {
        MyStruct *s = static_cast<MyStruct*>(MyList->Items[i]);

        if ((s->a >= 0x80 && s->a <= 0x9F) ||
            (s->a >= 0xA0 && s->a <= 0xAF) ||
            (s->a >= 0xB0 && s->a <= 0xBF) ||
            (s->a >= 0xE0 && s->a <= 0xEF))
        {
            MIDIEVENT *ev = reinterpret_cast<MIDIEVENT*>(&MyArr[elemSize * a]);
            ev->dwDeltaTime = s->deltaTime;
            ev->dwStreamID = 0;
            ev->dwEvent = (s->a << 0) | (s->b << 8) | (s->c << 16);
            ++a;
        }
    }

    HMIDISTRM out = NULL;
    UINT device_id = 0;
    MMRESULT err;
    MIDIPROPTIMEDIV prop = {};

    event = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (event)
    {
        err = midiStreamOpen(&out, &device_id, 1, reinterpret_cast<DWORD_PTR>(&midiCallback), 0, CALLBACK_FUNCTION);
        if (!err)
        {
            // Set the timebase. Here I use 96 PPQN
            prop.cbStruct = sizeof(prop);
            prop.dwTimeDiv = 96;
            midiStreamProperty(out, reinterpret_cast<LPBYTE>(&prop), MIDIPROP_SET|MIDIPROP_TIMEDIV);

            header.lpData = reinterpret_cast<LPSTR>(MyArr);
            header.dwBufferLength = header.dwBytesRecorded = (elemSize * a);
            //header.dwUser = 0;
            header.dwFlags = 0;
            //header.dwOffset = 0;

            err = midiOutPrepareHeader(reinterpret_cast<HMIDIOUT>(out), &header, sizeof(header));
            if (!err)
            {
                err = midiStreamOut(out, &header, sizeof(header));
                if (!err)
                {
                    err = midiStreamRestart(out);
                    if (!err)
                        WaitForSingleObject(event, INFINITE);
                }

                // Unprepare the buffer and MIDIHDR
                midiOutUnprepareHeader(reinterpret_cast<HMIDIOUT>(out), &header, sizeof(header));
            }

            midiStreamClose(out);
        }

        CloseHandle(event);
    }

    delete[] MyArr; // <-- omit if using std::vector or DynamicArray...
}


Have a look at MIDI Stream API, which covers this in more detail, especially the section on "The MIDIEVENT structure", which discusses this variable-length issue in long detail.
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1688
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: pointer to the first TList structure

Postby mark_c » Tue Jul 07, 2020 4:55 am

thanks Remy as always.
I simply used your suggestion and everything works now.

Code: Select all
struct alignas(MIDIEVENT) MIDIEVENT_NOPARMS
{
    DWORD dwDeltaTime;
    DWORD dwStreamID;
    DWORD dwEvent;
    //DWORD dwParms[];
};
mark_c
BCBJ Master
BCBJ Master
 
Posts: 243
Joined: Thu Jun 21, 2012 1:13 am

Re: pointer to the first TList structure

Postby mark_c » Mon Jul 13, 2020 12:03 pm

sorry Remy, but do you think that the Sleep () function is a kind of closed loop that takes up a lot of CPU?


We created our usleep() function to get mostly accurate MIDI timing which seemed to work well. Unfortunately, the usleep() function does not actually sleep but spins in a tight loop. This is a big waste of processor and still isn’t perfectly accurate. The Windows multimedia API supplies some mid-level APIs that can be used to make things easier on us and possibly our processor.

http://blog.fourthwoods.com/2012/02/24/ ... ws-part-5/
mark_c
BCBJ Master
BCBJ Master
 
Posts: 243
Joined: Thu Jun 21, 2012 1:13 am

Re: pointer to the first TList structure

Postby rlebeau » Mon Jul 13, 2020 2:23 pm

mark_c wrote:sorry Remy, but do you think that the Sleep () function is a kind of closed loop that takes up a lot of CPU?


The standard Win32 Sleep() function is very efficient, it does not eat up CPU cycles. But it is also not very accurate for multimedia usage, which is why the author of that article wrote his own usleep() function. The article also says:

The midiStream*() functions take a stream of MIDI messages and time values and takes care of processing the time values and playing the messages. This eliminates our need for usleep() and lets Windows take care of timing and playing individual messages. We still need to decode the MIDI events and time values and format them into the stream of MIDIEVENT structures that the API expects. However, the API will either provide its own timing and message processing or, if the device supports it, hand the buffer off to the MIDI device itself freeing up our CPU for other tasks. (much better than our tight usleep() loop!)
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1688
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: pointer to the first TList structure

Postby mark_c » Tue Jul 14, 2020 10:43 am

you're right Remy, I had missed how usleep () was made, I thought the author was referring to a Linux function.
But how does that author of that article still say that Sleep () is not accurate?
Does this mean that the Sleep () is not precise in the sense that, if Sleep (1000) ms is set, it could happen that sometimes the wait is greater and sometimes less?
mark_c
BCBJ Master
BCBJ Master
 
Posts: 243
Joined: Thu Jun 21, 2012 1:13 am

Re: pointer to the first TList structure

Postby rlebeau » Tue Jul 14, 2020 11:19 am

mark_c wrote:But how does that author of that article still say that Sleep () is not accurate?


Because it really is not accurate.

Even though it takes milliseconds as input, it does not have 1-millisecond precision. It is subject to the granularity of the system clock, which typically has a precision of 15..50-ish ms by default, depending on Windows version. And the input is more of a hint rather than an absolute. It can sleep for less than, or more than, the requested amount of time, depending on the system and timing. This is even mentioned in the Sleep() documentation.

Higher-precision timer loops are typically written in terms of high-resolution time stamps. However, multimedia code, such as a MIDI player, should use a multimedia timer instead.

mark_c wrote:Does this mean that the Sleep () is not precise in the sense that, if Sleep (1000) ms is set, it could happen that sometimes the wait is greater and sometimes less?


Yes.
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1688
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: pointer to the first TList structure

Postby mark_c » Mon Jul 20, 2020 4:27 am

thanks Remy,
really very strange that it is so complicated to use a simple timer even if it is as precise as you want.

https://www-user.tu-chemnitz.de/~heha/petzold/ch22d.htm
mark_c
BCBJ Master
BCBJ Master
 
Posts: 243
Joined: Thu Jun 21, 2012 1:13 am


Return to Technical

Who is online

Users browsing this forum: Google [Bot] and 31 guests

cron