MIDI extension for NME - unresolved external?

220 views
Skip to first unread message

bjorn...@gmail.com

unread,
May 11, 2013, 8:20:09 AM5/11/13
to haxe...@googlegroups.com
Hi all,

I'm looking into the possibility of creating a MIDI extension for Haxe, and having a wee bit of trouble compiling my very first test.
Basically, I've set out to wrap the cross-platform MIDI library portmidi (but it could just as well be another one) with higher-level functionality based on the Web MIDI API

I was thinking that since such a thing could be of use to several people, so I'm open to discuss the implementation and, well, generally just get this thing off the ground.

-- Here's where I am at right now --

I've looked into Joshua's tutorial on how to create an extension (very useful stuff).
I can compile the test project on my windows machine, and it tells me that 2 + 2 = 4 :-)

However, when I try throwing portmidi into the mix, I cannot get the thing to compile.
Inside my ExternalInterface.cpp, I've got the following code:

#ifndef IPHONE
#define IMPLEMENT_API
#endif

#if defined(HX_WINDOWS) || defined(HX_MACOS) || defined(HX_LINUX)
#define NEKO_COMPATIBLE
#endif

#ifndef STATIC_LINK
#define IMPLEMENT_API
#endif

#include <hx/CFFI.h>
#include "stdlib.h"
#include "portmidi.h"
#include <stdio.h>

static value pm_countdevices () {
   
   
return alloc_int (Pm_CountDevices() );
   
}
DEFINE_PRIM
(pm_countdevices, 0);


I try to call the "count devices" method of portmidi.c - this should be the simplest possible call you can make, with no initialization code needed:
Full source: http://portmedia.sourceforge.net/portmidi/doxygen/portmidi_8c-source.html

PMEXPORT int Pm_CountDevices( void ) {
   
Pm_Initialize();
   
/* no error checking -- Pm_Initialize() does not fail */
   
return pm_descriptor_index;
}

I know that portmidi itself is working here, as I can compile the various console tests that come with it...

Finally, my build file looks like this:

<xml>
   
   
<set name="GCC_THIN_ARCHIVE" value="1" if="iphone"/>
   
   
<include name="${HXCPP}/build-tool/BuildCommon.xml"/>
   
   
<set name="mobile" value="1" if="iphone"/>
   
<set name="mobile" value="1" if="android"/>
   
<set name="mobile" value="1" if="webos"/>
   
<set name="mobile" value="1" if="blackberry"/>
   
   
<set name="SLIBEXT" value=".lib" if="windows"/>
   
<set name="SLIBEXT" value=".a" unless="windows"/>
   
<set name="SLIBEXT" value=".so" if="webos"/>
   
   
<files id="common">
       
<compilerflag value="-Iinclude/pm_common"/>
       
<compilerflag value="-Iinclude/porttime"/>
       
<file name="common/ExternalInterface.cpp"/>
   
</files>

   
<files id="pm_win">
       
<compilerflag value="-Iinclude/pm_win"/>
   
</files>

   
   
<set name="tool" value="exe" unless="iphone"/>
   
<set name="tool" value="static" if="iphone"/>
   
   
<set name="IOS_ARCH" value="-v7" if="HXCPP_ARMV7"/>
   
   
<set name="ndll-tool" value="dll" unless="iphone"/>
   
<set name="ndll-tool" value="static" if="iphone"/>
   
<set name="name_extra" value=".iphoneos${IOS_ARCH}" if="iphoneos"/>
   
<set name="name_extra" value=".iphonesim" if="iphonesim"/>

   
<set name="no_thirdparty_extras" value="1" if="iphone"/>
   
   
<set name="sdl_static" value="1" unless="mobile"/>
   
<unset name="sdl_static" if="dynamic_sdl"/>
   
<set name="no_thirdparty_extras" value="1" if="dynamic_sdl"/>
   
   
<set name="name_prefix" value="lib" if="iphone" />
   
   
<target id="NDLL" output="${name_prefix}test${name_extra}" tool="linker" toolid="${ndll-tool}">
       
<outdir name="../ndll/${BINDIR}" />
       
<ext value=".ndll" unless="mobile"/>
       
<files id="common"/>
       
<files id="pm_win"/>
   
</target>

   
<target id="default">
       
<target id="NDLL"/>
   
</target>
   
</xml>


Once I build, this is the error I receive - but when looking into the ExternalInterface.obj file I can see references to portmidi?

> haxelib run hxcpp Build.xml
Setting environment for using Microsoft Visual Studio 2010 x86 tools.
link.exe -out:obj/lib/test.ndll -nologo -machine:x86 -dll -libpath:lib @all_objs

   Creating library obj/lib/test.lib and object obj/lib/test.exp
ExternalInterface.obj : error LNK2019: unresolved external symbol _Pm_CountDevices referenced in function "struct _value * __cdecl pm_countdevices(void)" (?pm_c
ountdevices@@YAPAU_value@@XZ)
obj/lib/test.ndll : fatal error LNK1120: 1 unresolved externals


bjorn...@gmail.com

unread,
May 11, 2013, 2:03:03 PM5/11/13
to haxe...@googlegroups.com
Oh, and just to clarify - the purpose of this extension is not to create a MIDI file format parser (musical score editor),
or a generator of synthesized audio (MIDI file playback). We (more or less) already have those things.

Rather, the concept is to have a cross-platform extension that could enable us to connect to external hardware,
such as synthesizers or drummachines, with the high precision timing that such a thing would require.

If curious about the scope of this undertaking, I suggest to take a quick look at the Web MIDI API specs,
they are among the more recently proposed (and simple!) API's from w3c, along with the Web Audio API.
As Portmidi itself is targeting Win/Mac/Linux, this project would limited to those platforms, but with the
promise that the API could seamlessly integrate with javascript in upcoming versions of Chrome, etc.

However: due to my inexperience with c++ compilers, I'm still stuck at the very first step,
getting the externals properly integrated into the compiled .ndll file (see first post).

I am however, learning a lot of stuff while trying to do so. For example, this code (from the portmidi tests)
is interesting in terms of obtaining high-precisision timing on the Windows platform.

BP

unread,
May 11, 2013, 9:16:19 PM5/11/13
to haxe...@googlegroups.com
hey! I think this sounds like a great idea.... i don't know if this project will help or not, but it supposedly makes writing the glue code for C stuff easier:


I've never used it, but saw it mentioned once and bookmarked it ;-)

Cambiata

unread,
May 12, 2013, 5:30:53 AM5/12/13
to haxe...@googlegroups.com
+1 for MIDI extension!

LouisBL

unread,
May 12, 2013, 6:03:25 AM5/12/13
to haxe...@googlegroups.com
Hi,

It seems that the error come from the linker, not the compiler.
If you want to compile portmidi from source, I think you need to add the portmidi.c file (and any other source file needed) with something like :
<file name="common/portmidi.c"/>

I think the best example is the nme Build.xml file: https://github.com/haxenme/NME/blob/master/project/Build.xml

I've managed to compile a freenect extension for Linux: https://github.com/louisbl/FreenectNME.
It took me a while to figure the correct parameters in the Build.xml, the easier way was to use a static library (libfreenect.a) compiled with the CMake toolchai instead of compiling all source with the hxcpp command.
Then I have to add "-Llib" flag to include the lib folder and "<lib name="-lfreenect />" to add the libfreenect.a file.
You can also have a look at https://github.com/hyperfiction/HypPhoton/blob/master/project/Build.xml. It's a  good example on how to include .lib files and compile extension for windows.

Hope it helps,

Louis

bjorn...@gmail.com

unread,
May 12, 2013, 1:42:55 PM5/12/13
to haxe...@googlegroups.com
Hey, nice to see that others would view this as a useful contribution :-)

> Then I have to add "-Llib" flag to include the lib folder and "<lib name="-lfreenect />" to add the libfreenect.a file.

that did the trick!! Note that I am using VS2010 C++, so that static library would translate to a .lib file instead of .a
But the example really had me confused. Somehow I though I had to go through the hxcpp step as well to compile that file. 

When running the neko runtime, it now tells me: "Number of devices: 30" (wow, I have *that* many MIDI ports?)

Great, next thing I'm going to try is to keep a "live" reference to portmidi, I am going to look at the SQL example (alloc_abstract) for that purpose.

bjorn...@gmail.com

unread,
May 12, 2013, 5:35:15 PM5/12/13
to haxe...@googlegroups.com
Next one is a CFFI related question:
How do I pass an array from one of the external interface methods?

I'm trying to collect the device information like this (method defined in ExternalInterface.cpp)

static value pm_enumerate_inputs () {
   
int i;
   
int num_devices = Pm_CountDevices();
    value v
;
    v
= alloc_array(num_devices);
   
for (i = 0; i < num_devices; i++) {
       
const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
        v
[i] = info->name;
   
}
   
return v;
}
DEFINE_PRIM
(pm_enumerate_inputs, 0);


But, this doesn't work. When I compile, I'm told that 'value' has an unknown size?
I know from my previous example that Pm_CountDevices will return 30, and I also tried hardcode the value.

Actually, this is a somewhat dumped-down example. I would like to be able to pass an array of structs, like this:
typedef struct _MIDIPort {
    std
::string name;
    std
::string type;
} MIDIPort;

MIDIPort *input_ports;

My limited knowledge of c++ tells me that I should return a pointer to the array, but I also failed in doing that.
Any help would be much appreciated!

Andreas Mokros

unread,
May 12, 2013, 6:12:31 PM5/12/13
to haxe...@googlegroups.com
Hi.

On Sun, 12 May 2013 14:35:15 -0700 (PDT)
bjorn...@gmail.com wrote:
> v[i] = info->name;

Did you try:
val_array_set_i(v, i, alloc_string(info->name));
instead?

--
Mockey

Andreas Mokros

unread,
May 12, 2013, 6:37:41 PM5/12/13
to haxe...@googlegroups.com
Hi.

On Sun, 12 May 2013 14:35:15 -0700 (PDT)
bjorn...@gmail.com wrote:
> Actually, this is a somewhat dumped-down example. I would like to be
> able to pass an array of structs, like this:
> typedef struct _MIDIPort {
> std::string name;
> std::string type;
> } MIDIPort;

I think you would pass an array of objects then, like:

value o = alloc_empty_object();
hxcpp_alloc_field(o, val_id("name"), alloc_string(info->name));
hxcpp_alloc_field(o, val_id("type"), alloc_string(info->type));
val_array_set_i(v, i, o);
...

--
Mockey

bjorn...@gmail.com

unread,
May 13, 2013, 1:45:06 AM5/13/13
to haxe...@googlegroups.com
Thanks Andreas, this is really helping me!

> val_array_set_i(v, i, alloc_string(info->name));

With this, the array arrives in haxe, but it has a type TUnknown. I have tried to see if I could cast it as an Array<String>, didn't help.
Tracing it shows [MidiDevice, MidiDevice, ...] - so the content is there, but it's playing hard to get ?!

> hxcpp_alloc_field(o, val_id("name"), alloc_string(info->name));

Doing this results in the following compiler error:
error C3861: 'hxcpp_alloc_field': identifier not found

But exchanging structured data is def. the way to go :-)

Andreas Mokros

unread,
May 13, 2013, 4:23:26 AM5/13/13
to haxe...@googlegroups.com
Hi.

On Sun, 12 May 2013 22:45:06 -0700 (PDT)
bjorn...@gmail.com wrote:
> With this, the array arrives in haxe, but it has a type TUnknown. I
> have tried to see if I could cast it as an Array<String>, didn't help.
> Tracing it shows [MidiDevice, MidiDevice, ...] - so the content is
> there, but it's playing hard to get ?!

How do you load your function? Maybe "MidiDevice" is the name you get?
Did you try to put other fields in the array?

> > hxcpp_alloc_field(o, val_id("name"), alloc_string(info->name));
> Doing this results in the following compiler error:
> error C3861: 'hxcpp_alloc_field': identifier not found

Was just a guess from CFFI.cpp.
Which version of hxcpp are you using? Did you try alloc_field instead?

> But exchanging structured data is def. the way to go :-)

Don't know. Is this really necessary like this? Maybe it's better to
wrap Pm_GetDeviceInfo?

--
Mockey

bjorn...@gmail.com

unread,
May 13, 2013, 7:45:36 AM5/13/13
to haxe...@googlegroups.com
 >How do you load your function? Maybe "MidiDevice" is the name you get?
> Did you try to put other fields in the array?

Ah, sorry I was being unclear. It contains my various device names, such as "Microsoft MIDI Mapper", "Automap MIDI", and so on.
I can trace this to the console, but using reflect, type method only tell me that it is of type TUnknown. I cannot iterate a TUnknown, so the "array" is basically useless to me.

Might as well transfer this data as a JSON string then? 

Anyway, I am out of my depth here ... CFFI documentation (http://haxe.org/doc/cpp/ffi#array-access) assumes a thourough understanding of both haxe and cpp.
I can only take this incrementally, one step at a time, and right now I think that JSON could in fact solve this problem for me.

However, the implementation will also need a callback method from c++ to haxe. Another "how to achieve this" for me :-D

Andreas Mokros

unread,
May 13, 2013, 9:16:40 AM5/13/13
to haxe...@googlegroups.com
Hi.

On Mon, 13 May 2013 04:45:36 -0700 (PDT)
bjorn...@gmail.com wrote:
> I can trace this to the console, but using reflect, type method only
> tell me that it is of type TUnknown. I cannot iterate a TUnknown, so
> the "array" is basically useless to me.

Uhm, are you compiling to neko? You get a NativeArray then. Use
neko.Lib.nekoToHaxe...

--
Mockey
Message has been deleted
Message has been deleted

LouisBL

unread,
May 13, 2013, 12:35:14 PM5/13/13
to haxe...@googlegroups.com
However, the implementation will also need a callback method from c++ to haxe. Another "how to achieve this" for me :-D
You need to pass a callback from haxe to c++, here is a little example.
In you Main.hx:
 
 
//Load the primitive to set the callback

var
cpp_cb_connect = Lib.load ("MyExtension", "hyp_on_connect", 1);

 //the callback
 function onConnectFromCpp( arg : String ) : Void {
  trace
( 'arg from cpp:$arg' );
 
}

 //set the callback
 function setCallback( ) : Void {
  cpp_cb_connect
( onConnectFromCpp );
 
}


In your ExternalInterface.cpp:
//Declare a reference to the callback
AutoGCRoot
*_on_connect         = 0;

extern "C"{
     //Call the haxe callback from C
     void hyp_on_connect( const char *socketId ) {
      val_call1( _on_connect -> get( ), alloc_string( socketId ) );
    }

   
//primitive to pass the callback from haxe
   
static
value hyp_cb_connect( value onCall ) {
    _on_connect = new AutoGCRoot( onCall );
    return alloc_bool(true);
   }
   DEFINE_PRIM(hyp_cb_connect,1);
}
In your main.cpp:

extern
"C" {
void hyp_on_connect( const char *socketId );
}

//when you want to call the haxe function
hyp_on_connect( "foo¨ );


In the FreenectNME extension I also pass an array by reference using the same way.
https://github.com/louisbl/FreenectNME/blob/master/project/common/ExternalInterface.cpp:
value freenectnme_set_depth_cb( value array, value onCall ){
depth_array = new AutoGCRoot( array );
depth_cb = new AutoGCRoot( onCall );
return alloc_bool( true );
}
DEFINE_PRIM( freenectnme_set_depth_cb, 2 );
https://github.com/louisbl/FreenectNME/blob/master/src/me/beltramo/freenect/FreenectNME.hx:

public static function setDepthCb( array : Array<Int>, cb : Void -> Void ) {
#if cpp
freenectnme_set_depth_cb( array, cb );
#end
}


bjorn...@gmail.com

unread,
May 13, 2013, 1:08:26 PM5/13/13
to haxe...@googlegroups.com
> You need to pass a callback from haxe to c++, here is a little example.

So, AutoGCRoot is in fact a pointer to Haxe? I wouldn't have guessed that. Thanks!

Speaking of garbage collection in general, this is definitely one of the things I need to read more about.
Especially since the portmidi code I'm working with is in fact multithreaded, this is important to get right.

bjorn...@gmail.com

unread,
May 13, 2013, 1:12:02 PM5/13/13
to haxe...@googlegroups.com
Hi Andreas,


> Uhm, are you compiling to neko?

since I'm trying to make an NME extension, it is not specifically Neko, no.
I'm not sure that would be the way to go, unless you suggest conditional compiling for each platform?

Andreas Mokros

unread,
May 13, 2013, 1:17:26 PM5/13/13
to haxe...@googlegroups.com
Hi.

On Mon, 13 May 2013 10:12:02 -0700 (PDT)
bjorn...@gmail.com wrote:
> since I'm trying to make an NME extension, it is not specifically
> Neko, no.

Your array problem looked like a neko-specific, that's why I asked.

--
Mockey

bjorn...@gmail.com

unread,
May 13, 2013, 1:26:10 PM5/13/13
to haxe...@googlegroups.com
Sure, and it's worth for me looking into it. If NativeArray is a good way to solve this for the Neko VM, then it's the way to go.
But ideally the extension is as platform-agnostic as possible.

Well, by now I've got plenty of code to try out and many failed attempts at getting things to work ahead of me. But to me, that is the best way to learn. 
Honestly, if I manage to get something working (and it's not too buggy :-), this list would be the reason why :-)

Cambiata

unread,
May 13, 2013, 3:55:45 PM5/13/13
to haxe...@googlegroups.com

But ideally the extension is as platform-agnostic as possible.

Looking forward to connecting my music notation solution to midi input and playback devices..! :-)
https://cambiatablog.wordpress.com/2012/09/06/multi-platform-music-notation-with-haxe-nme-part-1-goals/

J

Bjørn Næsby

unread,
May 13, 2013, 5:40:44 PM5/13/13
to haxe...@googlegroups.com
I've had some progress (been hacking away all night.. :-)

I can initialize portmidi via standard calls (select the input, output port), and I'm able to start the portmidi polling for messages by pressing a button in the UI.
Portmidi then starts a thread which run every millisecond, implemented via it's portime library that use timeSetEvent() on windows.

This is great, really great. The only problem is, how to stop this process?

Let me try and explain the setup:Portmidi does the actual polling inside the ExternalInterface I've created (a standard c implementation)
This of course will block the process, as it's based on a while(active) loop.

To work around this, I'm creating a nonblocking process call to c++ via Haxe / neko.vm.Thread
But - when I try to stop the process by calling a special method that cancel out the active flag (done by injecting a special value into the MIDI stream, which should be thread-safe), I get an exception.

If anyone's interested in taking a look at the source I post it ... tomorrow (gotta get some sleep now).

Catch you later,
Bjorn



--
To post to this group haxe...@googlegroups.com
http://groups.google.com/group/haxelang?hl=en
---
You received this message because you are subscribed to a topic in the Google Groups "Haxe" group.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Reply all
Reply to author
Forward
0 new messages