15 Commits
0.2 ... master

9 changed files with 383 additions and 22 deletions
Split View
  1. +100
    -0
      OSC2AHK/OSC2AHK.rc
  2. +6
    -0
      OSC2AHK/OSC2AHK.vcxproj
  3. +8
    -0
      OSC2AHK/OSC2AHK.vcxproj.filters
  4. +83
    -9
      OSC2AHK/dllmain.cpp
  5. +3
    -0
      OSC2AHK/dllmain.h
  6. +14
    -0
      OSC2AHK/resource.h
  7. +85
    -8
      README.md
  8. +45
    -0
      examples/sending_example.ahk
  9. +39
    -5
      msgtest.ahk

+ 100
- 0
OSC2AHK/OSC2AHK.rc View File

@ -0,0 +1,100 @@
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// German (Germany) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_DEU)
LANGUAGE LANG_GERMAN, SUBLANG_GERMAN
#pragma code_page(1252)
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x2L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "000904b0"
BEGIN
VALUE "CompanyName", "Eleton Audio"
VALUE "FileDescription", "AutoHotkey OSC integration"
VALUE "FileVersion", "1.0.0.0"
VALUE "InternalName", "OSC2AHK.dll"
VALUE "LegalCopyright", "Copyright (C) 2021"
VALUE "OriginalFilename", "OSC2AHK.dll"
VALUE "ProductName", "OSC2AHK"
VALUE "ProductVersion", "1.0.0.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x9, 1200
END
END
#endif // German (Germany) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

+ 6
- 0
OSC2AHK/OSC2AHK.vcxproj View File

@ -152,12 +152,15 @@
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies);winmm.lib;Ws2_32.lib;</AdditionalDependencies>
<Version>
</Version>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="dllmain.h" />
<ClInclude Include="framework.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
@ -173,6 +176,9 @@
<Project>{e8c1fcbc-132a-47bb-a02b-4468ddb55e6c}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="OSC2AHK.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>

+ 8
- 0
OSC2AHK/OSC2AHK.vcxproj.filters View File

@ -24,6 +24,9 @@
<ClInclude Include="dllmain.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
@ -33,4 +36,9 @@
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="OSC2AHK.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>

+ 83
- 9
OSC2AHK/dllmain.cpp View File

@ -22,6 +22,7 @@ protected:
handleOscMsg(m);
}
catch (osc::Exception& e) {
(void)e;
// any parsing errors such as unexpected argument types, or
// missing arguments get thrown as exceptions.
OutputDebugString(L"ProcessMessage: Error while parsing message: ...");
@ -63,11 +64,16 @@ void runOscThread(unsigned int port)
thePacketListener = new ThePacketListener;
if (sock) sock->~UdpListeningReceiveSocket();
sock = new UdpListeningReceiveSocket(
IpEndpointName(IpEndpointName::ANY_ADDRESS, port),
thePacketListener);
sock->RunUntilSigInt(); //<<--- this is the loop
try {
sock = new UdpListeningReceiveSocket(
IpEndpointName(IpEndpointName::ANY_ADDRESS, port),
thePacketListener);
sock->RunUntilSigInt(); //<<--- this is the loop
}
catch (std::runtime_error)
{
OutputDebugString(L"Unable to open port!\r\n");
}
}
/* DLL was loaded */
@ -79,12 +85,16 @@ BOOL APIENTRY DllMain( HMODULE hModule,
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
OutputDebugString(L"DLL_PROCESS_ATTACH\r\n");
break;
case DLL_THREAD_ATTACH:
OutputDebugString(L"DLL_THREAD_ATTACH\r\n");
break;
case DLL_THREAD_DETACH:
OutputDebugString(L"DLL_THREAD_DETACH\r\n");
break;
case DLL_PROCESS_DETACH:
OutputDebugString(L"DLL_THREAD_DETACH\r\n");
close();
break;
}
@ -99,12 +109,39 @@ DLLEXPORT int open(HWND targetWindowHandle, unsigned int port)
if (!port) OutputDebugString(L"open: port!!\r\n");
return 1;
}
//Check if sock is currently free to use
if (sock)
{
if (sock->IsBound()) //Sock is currently opened. User should call close() first!
{
OutputDebugString(L"open: seems to be opened already!\r\n");
return 3;
}
else //Probably something went wrong in UDP socket, just try closing and reopening it...
{
close(0);
}
}
unsigned int timeout = 500; //timout for port opening, in ms
/*Store handle to Autohotkey window globally*/
hwnd = targetWindowHandle;
/*Start OSC Thread*/
oscThread = new std::thread(runOscThread, port);
while ((!sock || !sock->IsBound()) && timeout)
{
timeout--;
Sleep(1);
}
if (!sock || !sock->IsBound())
{
OutputDebugString(L"open: cannot open port!!\r\n");
return 2;
}
OutputDebugString(L"open: Opened.\r\n");
return 0;
}
@ -115,7 +152,9 @@ DLLEXPORT int close(unsigned int clearListeners)
{
sock->AsynchronousBreak();
}
oscThread->join();
if(oscThread) oscThread->join();
delete sock;
sock = nullptr;
oscThread = NULL;
hwnd = NULL;
if (clearListeners) listeners.clear();
@ -125,10 +164,15 @@ DLLEXPORT int close(unsigned int clearListeners)
DLLEXPORT int addListener(LPCSTR address_, unsigned int messageID_, unsigned int dataType_)
{
std::string addrStr(address_);
//All OSC addresses have to start with a '/'
if (addrStr[0] != '/') addrStr.insert(0, "/");
OutputDebugString(L"addListener: address=");
OutputDebugStringA(address_);
OutputDebugStringA(addrStr.c_str());
OutputDebugString(L"\r\n");
listeners.push_back(Listener{ std::string(address_), dataType_, messageID_ });
listeners.push_back(Listener{ addrStr, dataType_, messageID_ });
return 0;
}
@ -180,6 +224,16 @@ DLLEXPORT void sendOscMessageInt(char* ip, unsigned int port, char* address, int
transmitSocket.Send(p.Data(), p.Size());
}
DLLEXPORT void sendOscMessageInt2(char* ip, unsigned int port, char* address, int payload1, int payload2)
{
UdpTransmitSocket transmitSocket(IpEndpointName(ip, port));
char buffer[1024];
osc::OutboundPacketStream p(buffer, 1024);
p << osc::BeginMessage(address) << payload1 << payload2 << osc::EndMessage;
transmitSocket.Send(p.Data(), p.Size());
}
DLLEXPORT void sendOscMessageFloat(char* ip, unsigned int port, char* address, float payload)
{
UdpTransmitSocket transmitSocket(IpEndpointName(ip, port));
@ -190,6 +244,16 @@ DLLEXPORT void sendOscMessageFloat(char* ip, unsigned int port, char* address, f
transmitSocket.Send(p.Data(), p.Size());
}
DLLEXPORT void sendOscMessageFloat2(char* ip, unsigned int port, char* address, float payload1, float payload2)
{
UdpTransmitSocket transmitSocket(IpEndpointName(ip, port));
char buffer[1024];
osc::OutboundPacketStream p(buffer, 1024);
p << osc::BeginMessage(address) << payload1 << payload2 << osc::EndMessage;
transmitSocket.Send(p.Data(), p.Size());
}
DLLEXPORT void sendOscMessageString(char* ip, unsigned int port, char* address, char* payload)
{
UdpTransmitSocket transmitSocket(IpEndpointName(ip, port));
@ -200,6 +264,16 @@ DLLEXPORT void sendOscMessageString(char* ip, unsigned int port, char* address,
transmitSocket.Send(p.Data(), p.Size());
}
DLLEXPORT void sendOscMessageString2(char* ip, unsigned int port, char* address, char* payload1, char* payload2)
{
UdpTransmitSocket transmitSocket(IpEndpointName(ip, port));
char buffer[1024];
osc::OutboundPacketStream p(buffer, 1024);
p << osc::BeginMessage(address) << payload1 << payload2 << osc::EndMessage;
transmitSocket.Send(p.Data(), p.Size());
}
void removeStoredString(int stringId)
{
for (UINT i = 0; i < storedStrings.size(); i++)
@ -479,7 +553,7 @@ int handleOscMsg(const osc::ReceivedMessage& m)
/* Debugging function to test messaging. Probably will be removed later. */
DLLEXPORT int testMsg(HWND windowHandle, unsigned int messageID)
{
float theFloat = 1.01;
float theFloat = (float)1.01;
int lParam = reinterpret_cast<int&>(theFloat);
PostMessage(hwnd, 0x1002, 0, lParam); //post to message queue


+ 3
- 0
OSC2AHK/dllmain.h View File

@ -25,8 +25,11 @@ extern "C" DLLEXPORT int addListener(LPCSTR address, unsigned int messageID, uns
extern "C" DLLEXPORT int removeListener(LPCSTR address);
extern "C" DLLEXPORT char* getStringData(char* targetString, unsigned int targetSize, unsigned int StringID);
extern "C" DLLEXPORT void sendOscMessageInt(char* ip, unsigned int port, char* address, int payload);
extern "C" DLLEXPORT void sendOscMessageInt2(char* ip, unsigned int port, char* address, int payload1, int payload2);
extern "C" DLLEXPORT void sendOscMessageFloat(char* ip, unsigned int port, char* address, float payload);
extern "C" DLLEXPORT void sendOscMessageFloat2(char* ip, unsigned int port, char* address, float payload1, float payload2);
extern "C" DLLEXPORT void sendOscMessageString(char* ip, unsigned int port, char* address, char* payload);
extern "C" DLLEXPORT void sendOscMessageString2(char* ip, unsigned int port, char* address, char* payload1, char* payload2);
int handleOscMsg(const osc::ReceivedMessage& m);
bool isMatchingOscType(unsigned int msgType, unsigned int listenerTypeField);
bool isMatchingOSCAddress(const char* address, const char* pattern);


+ 14
- 0
OSC2AHK/resource.h View File

@ -0,0 +1,14 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by OSC2AHK.rc
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 101
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

+ 85
- 8
README.md View File

@ -1,20 +1,37 @@
# OSC2AHK
## OSC2AHK
This is a DLL extension for [AutoHotkey](https://www.autohotkey.com/) that enables support for OSC (Open Sound Control).
## Work in progress
While most functions should already work as intended, there may still be some bugs. If you encounter some possibly wrong behaviour, please let us know!
* OSC address wildcards are supported now but probably need some testing
* Documentation could be improved (sending functions)
## Table of contents
- [OSC2AHK](#osc2ahk)
- [Work in progress](#work-in-progress)
- [Table of contents](#table-of-contents)
- [Installation](#installation)
- [Usage](#usage)
- [General concept](#general-concept)
- [Receive functions](#receive-functions)
- [Load DLL](#load-dll)
- [Open network port](#open-network-port)
- [Close network port](#close-network-port)
- [Add listener](#add-listener)
- [Remove listener](#remove-listener)
- [Get string data](#get-string-data)
- [Send functions](#send-functions)
- [Send integer message](#send-integer-message)
- [Send float message](#send-float-message)
- [Send string message](#send-string-message)
- [Credits](#credits)
## Installation
Just download the latest version of OSC2AHK.dll from the [releases page](https://files.eleton-audio.de/gitea/Ludwig/OSC2AHK/releases) and place it in the same directory as your AutoHotkey scripts. Maybe you also want to check out the provided [usage examples](https://files.eleton-audio.de/gitea/Ludwig/OSC2AHK/src/branch/master/examples).
Just download the latest version of OSC2AHK.dll from the [releases page](https://files.eleton-audio.de/gitea/Ludwig/OSC2AHK/releases) and place it in the same directory as your AutoHotkey scripts. The DLL provided in the Releases section at the moment only works on 64 bit systems, Windows 10 or Windows 11. Other systems are not supported right now. Maybe you also want to check out the provided [usage examples](https://files.eleton-audio.de/gitea/Ludwig/OSC2AHK/src/branch/master/examples).
## Usage
### General concept
After the DLL is loaded and a network port is opened with the [`open()`](https://files.eleton-audio.de/gitea/Ludwig/OSC2AHK#open-network-port) function, it receives all incoming OSC messages. To specify the messages that should be passed to the AutoHotkey (AHK) script, "listeners" are specified and created by the ['addListener()'](https://files.eleton-audio.de/gitea/Ludwig/OSC2AHK#add-listener) function. When a OSC message is received that matches a existing listener, a "system message" is sent to the AHK script. This happens via the Windows message queue and the system message ID is used to tell the script which OSC address was received. Therefore with [`addListener()`](https://files.eleton-audio.de/gitea/Ludwig/OSC2AHK#add-listener) a ID has to be passed to the DLL which then uses this ID to post the system message.
### Functions
### Receive functions
These functions are the main functions used to receive OSC messages in AHK.
#### Load DLL
First the DLL should be loaded from within the AHK script by calling
```
@ -27,6 +44,7 @@ DllCall("LoadLibrary", "Str", "OSC2AHK.dll", "Ptr")
int open(HWND targetWindowHandle, unsigned int port);
//targetWindowHandle: Handle to the calling window to which messages on received OSC data is sent
//port: Network port to open
//returns: Zero on success, something else on failure
```
This opens the network port and enables the DLL to receive OSC messages.
@ -34,7 +52,9 @@ Example AHK snippet:
```
Gui +LastFound
hWnd := WinExist()
DllCall("OSC2AHK.dll\open", UInt, hWnd, UInt, 7002)
success := DllCall("OSC2AHK.dll\open", UInt, hWnd, UInt, 7002)
if (success != 0)
msgbox, Failed to open port!
```
#### Close network port
@ -102,6 +122,18 @@ msghandlerTest1(oscType, data, msgID, hwnd)
```
Note that you may add many listeners, for any combination of the same/different OSC addresses, datatypes and system message IDs. Also, in AutoHotkey you may add multiple callback functions for the same system message ID or multiple system message IDs to the same callback function. This creates a big flexibility but to keep it less clutterd, such multiple routing paths should be avoided where possible.
For advanced usage, the address pattern passed to `addListener()` supports standard OSC wildcards, so one listener can match multiple messages that share some simliarities. These wildcards are supported by the OSC 1.0 standard:
> * '?' in the OSC Address Pattern matches any single character
> * '*' in the OSC Address Pattern matches any sequence of zero or more characters
> * A string of characters in square brackets (e.g., "[string]") in the OSC Address Pattern matches any character in the string. Inside square brackets, the minus sign (-) and exclamation point (!) have special meanings:
> * two characters separated by a minus sign indicate the range of characters between the given two in ASCII collating sequence. (A minus sign at the end of the string has no special meaning.)
> * An exclamation point at the beginning of a bracketed string negates the sense of the list, meaning that the list matches any character not in the list. (An exclamation point anywhere besides the first character after the open bracket has no special meaning.)
> * A comma-separated list of strings enclosed in curly braces (e.g., "{foo,bar}") in the OSC Address Pattern matches any of the strings in the list.
> * Any other character in an OSC Address Pattern can match only the same character.
>
> -- <cite>[http://opensoundcontrol.org/]</cite>
#### Remove listener
Of course, listeners also can be removed in a similar way:
```cpp
@ -130,5 +162,50 @@ msghandlerString(oscType, data, msgID, hwnd)
}
```
### Send functions
The DLL also can be used to transmit OSC messages from AutoHotkey by using the following functions. Usually there is only one argument (payload) per message, but the functions ending in "...2" allow the sending of two arguments in one message. Only two arguments of the same type are supported at the moment.
#### Send integer message
```cpp
void sendOscMessageInt(char* ip, unsigned int port, char* address, int payload);
void sendOscMessageInt2(char* ip, unsigned int port, char* address, int payload1, int payload2);
```
Example call from AHK:
```
ip := "192.168.1.2"
port := 8002
addr := "/testmsg"
data := 42
DllCall("OSC2AHK.dll\sendOscMessageInt", AStr, ip, UInt, port, AStr, addr, Int, data)
```
#### Send float message
```cpp
void sendOscMessageFloat(char* ip, unsigned int port, char* address, float payload);
void sendOscMessageFloat2(char* ip, unsigned int port, char* address, float payload1, float payload2);
```
Example call from AHK:
```
ip := "192.168.1.2"
port := 8002
addr := "/my/msg"
data := 42.3
DllCall("OSC2AHK.dll\sendOscMessageFloat", AStr, ip, UInt, port, AStr, addr, Float, data)
```
#### Send string message
```cpp
void sendOscMessageString(char* ip, unsigned int port, char* address, char* payload);
void sendOscMessageString2(char* ip, unsigned int port, char* address, char* payload1, char* payload2);
```
Example call from AHK:
```
ip := "192.168.1.2"
port := 8002
addr := "/some/message"
data := "This is the string"
DllCall("OSC2AHK.dll\sendOscMessageString", AStr, ip, UInt, port, AStr, addr, AStr, data)
```
## Credits
This DLL uses the [oscpack](http://www.rossbencina.com/code/oscpack) OSC implementation by Ross Bencina.

+ 45
- 0
examples/sending_example.ahk View File

@ -0,0 +1,45 @@
; This example sends OSC messages on key presses and works together with the simple_example.ahk
; and string_example.ahk. The messages are sent back to this PC by using the loopback IP adress
; 127.0.0.1, but of course normal IP addresses can be used too.
#NoEnv
; #Warn
SendMode Input
SetWorkingDir %A_ScriptDir% ; Until here, this is the default script template
; Get handle to this running script instance
Gui +LastFound
hWnd := WinExist()
; Load DLL
DllCall("LoadLibrary", "Str", "OSC2AHK.dll", "Ptr")
; Send OSC message with integer payload with Shift+a
+a::
ip := "127.0.0.1" ; Note that this is the "loopback" IP, so this gets sent back to our PC
port := 7001
addr := "/test1"
data := 42
DllCall("OSC2AHK.dll\sendOscMessageInt", AStr, ip, UInt, port, AStr, addr, Int, data)
return
; Send OSC message with float payload with Shift+s
+s::
ip := "127.0.0.1" ; Note that this is the "loopback" IP, so this gets sent back to our PC
port := 7001
addr := "/test1"
data := 42.3
DllCall("OSC2AHK.dll\sendOscMessageFloat", AStr, ip, UInt, port, AStr, addr, Float, data)
return
; Send OSC message with string payload with Shift+d
+d::
ip := "127.0.0.1" ; Note that this is the "loopback" IP, so this gets sent back to our PC
port := 7001
addr := "/test1"
data := "Some string..."
DllCall("OSC2AHK.dll\sendOscMessageString", AStr, ip, UInt, port, AStr, addr, AStr, data)
return
; Shutdown the script with Shift+ESC
+Esc::
ExitApp

+ 39
- 5
msgtest.ahk View File

@ -21,7 +21,9 @@ DllCall("LoadLibrary", "Str", "x64\Debug\OSC2AHK.dll", "Ptr")
OnMessage(0x1002, "msghandlerFloat")
DllCall("OSC2AHK.dll\open", UInt, hWnd, UInt, 7002)
success := DllCall("OSC2AHK.dll\open", UInt, hWnd, UInt, 7003)
if (success != 0)
msgbox, Failed to open port!
DllCall("OSC2AHK.dll\addListener", AStr, "/test1", UInt, 0x1001, UInt, oscTypeInt)
OnMessage(0x1001, "msghandlerInt")
@ -122,10 +124,42 @@ do_exit:
Esc::
ExitApp
^a::
VarSetCapacity(theStr, 10)
theStr := DllCall("OSC2AHK.dll\getStringData", AStr, theStr, UInt, 10, UInt, 0, "Cdecl AStr")
msgbox,%theStr%
+a::
;ret := DllCall("OSC2AHK.dll\open", UInt, hWnd, UInt, 7002)
;MsgBox, %ret%
ip := "127.0.0.1"
port := 8002
addr := "/testmsg"
data := 42
data2 := 43
DllCall("OSC2AHK.dll\sendOscMessageInt", AStr, ip, UInt, port, AStr, addr, Int, data)
return
+s::
ip := "127.0.0.1"
port := 8002
addr := "/float/msg"
data := 42.3
data2 := 54.4
DllCall("OSC2AHK.dll\sendOscMessageFloat2", AStr, ip, UInt, port, AStr, addr, Float, data, Float, data2)
return
+d::
ip := "127.0.0.1"
port := 8002
addr := "/msg/string"
data := "This is the string"
data2:= "Second string"
DllCall("OSC2AHK.dll\sendOscMessageString2", AStr, ip, UInt, port, AStr, addr, AStr, data, AStr, data2)
return
+f::
ip := "127.0.0.1"
port := 8002
addr := "/msg/string"
data := "This is the string"
DllCall("OSC2AHK.dll\sendOscMessageString", AStr, ip, UInt, port, AStr, addr, AStr, data)
return
; DllCall("OSC2AHK.dll\close", UInt, 0)
;msgbox,esc
; DllCall("OSC2AHK.dll\open", UInt, hWnd, UInt, 7001)

Loading…
Cancel
Save