26 Commits
0.1 ... master

Author SHA1 Message Date
  Ludwig Frühschütz 83e36c0925 Readme: Add note that the dll is 64bit only 2 years ago
  Ludwig Frühschütz 018c9d90b3 Readme update 2 years ago
  Ludwig Frühschütz cdfa413d09 Add "/" to begin of OSC address if user doesn't. closes #6 2 years ago
  Ludwig Frühschütz ee649473c3 Bumped version to 1.0 2 years ago
  Ludwig Frühschütz 1841ebb220 Improved open() and close() functions to be able to repeatedly open and close udp port 2 years ago
  Ludwig Frühschütz 406baef516 Addes OSC sending functions with two arguments 2 years ago
  Ludwig Frühschütz 6bd8687a45 Fixed compiler warnings 2 years ago
  Ludwig Frühschütz c0f962d2be Added version information to DLL 3 years ago
  Ludwig Frühschütz 72ab309d5e Added sending example 3 years ago
  Ludwig Frühschütz c222f2855e Readme: Updated TOC 3 years ago
  Ludwig Frühschütz 463d0775eb Readme: Added TOC 3 years ago
  Ludwig Frühschütz 372d9426bf Readme: Added OSC wildcards 3 years ago
  Ludwig Frühschütz 6c4a3211dc Readme: Added sending functions 3 years ago
  Ludwig Frühschütz 6fa9150816 Readme: Added open() return value 3 years ago
  Ludwig Frühschütz 31c53da91f Catch runtime error when port cannot be opened. closes #5 3 years ago
  Ludwig Frühschütz 898bd1200d Readme: Updated project state 3 years ago
  Ludwig Frühschütz 3b36d8b689 Added OSC send message functions. Also fixed oscpack address string size check. 3 years ago
  Ludwig Frühschütz a6992a45c8 Added OSC address wildcard support 3 years ago
  Ludwig Frühschütz 670c1f7ff4 Readme: Updated string retrievement 3 years ago
  Ludwig Frühschütz d10654555a Added string example 3 years ago
  Ludwig Frühschütz db4ca91180 Fixed heap corruption in string retrievement, also avoid runtime error if string doesnt fit buffer 3 years ago
  Ludwig Frühschütz 4dfb45c5f3 edited readme 3 years ago
  Ludwig Frühschütz b2dd473bd0 readme: added link to examples 3 years ago
  Ludwig Frühschütz 782cb439e5 added example ahk script 3 years ago
  Ludwig Frühschütz b3ba1a0f84 Updated usage part of readme 3 years ago
  Ludwig Frühschütz 88d90df3f1 Add installation instructions to readme 3 years ago
12 changed files with 657 additions and 47 deletions
Split View
  1. +100
    -0
      OSC2AHK/OSC2AHK.rc
  2. +6
    -0
      OSC2AHK/OSC2AHK.vcxproj
  3. +8
    -0
      OSC2AHK/OSC2AHK.vcxproj.filters
  4. +249
    -11
      OSC2AHK/dllmain.cpp
  5. +8
    -0
      OSC2AHK/dllmain.h
  6. +14
    -0
      OSC2AHK/resource.h
  7. +94
    -28
      README.md
  8. +45
    -0
      examples/sending_example.ahk
  9. +47
    -0
      examples/simple_example.ahk
  10. +44
    -0
      examples/string_example.ahk
  11. +40
    -6
      msgtest.ahk
  12. +2
    -2
      oscpack/osc/OscOutboundPacketStream.cpp

+ 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>

+ 249
- 11
OSC2AHK/dllmain.cpp View File

@ -6,6 +6,7 @@
#include <thread>
#include "osc/OscReceivedElements.h"
#include "osc/OscPacketListener.h"
#include "osc/OscOutboundPacketStream.h"
#include "ip/UdpSocket.h"
//Class from OSC library (oscpack). This calls our handleOscMsg() on every new message.
@ -21,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: ...");
@ -62,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 */
@ -78,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;
}
@ -98,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;
}
@ -114,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();
@ -124,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;
}
@ -156,7 +201,10 @@ DLLEXPORT char* getStringData(char* targetString, unsigned int targetSize, unsig
{
if (storedStrings[i].id == stringId)
{
strcpy_s(targetString, targetSize, storedStrings[i].string.c_str());
if (storedStrings[i].string.length() < targetSize) //Avoid runtime error.
strcpy_s(targetString, targetSize, storedStrings[i].string.c_str());
else
strcpy_s(targetString, targetSize, "\0"); //String doesnt fit, return empty string"
storedStrings.erase(storedStrings.begin() + i);
return targetString;
}
@ -166,6 +214,66 @@ DLLEXPORT char* getStringData(char* targetString, unsigned int targetSize, unsig
return targetString;
}
DLLEXPORT void sendOscMessageInt(char* ip, unsigned int port, char* address, int payload)
{
UdpTransmitSocket transmitSocket(IpEndpointName(ip, port));
char buffer[1024];
osc::OutboundPacketStream p(buffer, 1024);
p << osc::BeginMessage(address) << payload << osc::EndMessage;
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));
char buffer[1024];
osc::OutboundPacketStream p(buffer, 1024);
p << osc::BeginMessage(address) << payload << osc::EndMessage;
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));
char buffer[1024];
osc::OutboundPacketStream p(buffer, 1024);
p << osc::BeginMessage(address) << payload << osc::EndMessage;
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++)
@ -216,6 +324,136 @@ bool isMatchingOscType(unsigned int msgType, unsigned int listenerTypeField)
return (listenerTypeField & msgType);
}
/* Checks if an incoming OSC messages address matches a given pattern.
* Also handles OSC wildcards! */
bool isMatchingOSCAddress(const char* address, const char* pattern)
{
//If no wildcards in pattern, do a simple strcmp to save time
if (!containsOscWildcard(pattern))
{
if (strcmp(address, pattern) == 0) return true;
else return false;
}
std::string chars;
while (*address != '\0' && *pattern != '\0')
{
switch (*pattern)
{
case '?': //exactly one character
if (*address == '/') return false; //'?' does not match beyond '/'
address++;
pattern++;
break;
case '*': //zero or more chars
pattern++;
if (isMatchingOSCAddress(address, pattern)) return true; //'*' matches "no char"
while (*address != '\0' && *address != '/')
{ //shift address one char and check again (recursion)
address++;
if (isMatchingOSCAddress(address, pattern)) return true;
}
return false;
break;
case '[': //Match one character from list (or range of) chars like [asdf] or [a-f] or [!0-9]
pattern++;
while (*pattern != '\0' &&
*pattern != ']' &&
*pattern != '/')
{
if (*pattern == '-') //range of chars
{
pattern++;
char c = chars.back() + 1;
if (*pattern == ']' || *pattern == '/' || *pattern == '\0') c = *pattern;
while (c != *pattern)
{
chars.push_back(c);
c++;
}
}
chars.push_back(*pattern);
pattern++;
}
if (chars.at(0) == '!') //negated list or range of chars
{
chars.erase(chars.begin());
if (chars.find(*address) != std::string::npos) return false;
}
else if (chars.find(*address) == std::string::npos) return false;
chars.clear();
if (*pattern == ']') pattern++;
address++;
break;
case '{': //list of strings (comma separated like {asd,fghj,etc}
bool result;
result = false;
pattern++;
while (!result &&
*pattern != '\0' &&
*pattern != '}' &&
*pattern != '/')
{
while (*pattern != '\0' &&
*pattern != ',' &&
*pattern != '}' &&
*pattern != '/')
{
chars.push_back(*pattern);
pattern++;
}
if (chars.compare(0, chars.length(), address, chars.length()) == 0)
{
result = true;
address += chars.length();
}
chars.clear();
pattern++;
}
if (!result) return false;
while (*pattern != '\0' &&
*pattern != '}' &&
*pattern != '/')
{
pattern++;
}
if (*pattern == '}') pattern++;
break;
default: //Just a character, no wildcard here
if (*pattern == *address)
{
address++;
pattern++;
}
else
{
return false;
}
break;
}
}
if (*pattern != *address) return false; //Only match if both strings end here.
return true; //No earlier 'return false' triggered, so we got a match!
}
bool containsOscWildcard(const char* pattern)
{
while (*pattern != '\0')
{
if (*pattern == '?' ||
*pattern == '*' ||
*pattern == '[' ||
*pattern == ']' ||
*pattern == '{' ||
*pattern == '}') return true;
pattern++;
}
return false;
}
int handleOscMsg(const osc::ReceivedMessage& m)
{
int ret = 0;
@ -238,7 +476,7 @@ int handleOscMsg(const osc::ReceivedMessage& m)
for (UINT i = 0; i < listeners.size(); i++)
{
//Check if incoming OSC address matches current listener entry
if (listeners[i].address.compare(m.AddressPattern()) == 0) //TODO: Implement OSC Pattern Wildcards
if ( isMatchingOSCAddress(m.AddressPattern(), listeners[i].address.c_str()) )
{
//Check payload type
if (m.ArgumentCount() == 0) {
@ -315,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


+ 8
- 0
OSC2AHK/dllmain.h View File

@ -24,8 +24,16 @@ extern "C" DLLEXPORT int close(unsigned int clearListeners = 1);
extern "C" DLLEXPORT int addListener(LPCSTR address, unsigned int messageID, unsigned int dataType = OSC_TYPE_ALL);
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);
bool containsOscWildcard(const char* pattern);
unsigned int getOscType(osc::ReceivedMessage::const_iterator arg);
void removeStoredString(int stringId);
int addStoredString(std::string theString);


+ 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

+ 94
- 28
README.md View File

@ -1,18 +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
Currently this project is unfinished, there may be missing functionality and errors may occur!
* After multiple calls of [`getStringData()`](https://files.eleton-audio.de/gitea/Ludwig/OSC2AHK#get-string-data) a heap memory fault occurs. This could also be a error in the calling AHK script.
* OSC address wildcards are not supported yet
* Documentation could be improved
## 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. 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
```
@ -25,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.
@ -32,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
@ -68,7 +90,7 @@ This adds a new listener for a OSC address pattern, only OSC messages with a exi
#define OSC_TYPE_COLOR 1024
#define OSC_TYPE_ALL UINT_MAX //=0xffffffff
```
Add listener from AHK script:
Add a listener from the AHK script:
```
; just for convenience, you also could use the "magic numbers" directly
global oscTypeInt := 2
@ -98,6 +120,19 @@ 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:
@ -110,36 +145,67 @@ DllCall("OSC2AHK.dll\removeListener", AStr, "/test1")
```
#### Get string data
__Work in progress, this is not working properly for now__
As string data carried by OSC messages may be of varying length, they cannot directly be passed by system messages. Therefore, the string is buffered in the DLL and the `data` variable of the callback function receives a unique identifier to the stored string. This string then can be retreived later on by the AHK script by calling this function.
As string data carried by OSC messages may be of varying length and cannot directly be passed by system messages. Therefore, the string is buffered in the DLL and the `data` variable of the callback function receives a unique identifier to the stored string. This string then can be retrieved later on from the AHK script by calling this function.
```cpp
char* getStringData(char* targetString, unsigned int targetSize, unsigned int StringID);
```
This function writes the stored string to the `targetString` and also removes it from the buffer inside the DLL.
A example AHK snippet could look like this:
A callback functin in AHK could look like this (also look at the [string example](https://files.eleton-audio.de/gitea/Ludwig/OSC2AHK/src/branch/master/examples/string_example.ahk)):
```
global oscTypeString := 8
DllCall("OSC2AHK.dll\addListener", AStr, "/test2", UInt, 0x1004, UInt, oscTypeString)
OnMessage(0x1004, "msghandlerString")
msghandlerString(oscType, data, msgID, hwnd)
{
if (wParam != oscTypeString)
{ ; not a string!
return
}
; We were notified that a OSC string message with the specified address was received
; and the string payload was stored with the ID passed by 'data'.
; Now we will retrieve this string from the DLL.
VarSetCapacity(theStr, 20)
theStr := DllCall("OSC2AHK.dll\getStringData", AStr, theStr, UInt, 20, UInt, lParam, "Cdecl AStr")
VarSetCapacity(theString, 20)
theString := DllCall("OSC2AHK.dll\getStringData", str, theString, UInt, 20, UInt, data, AStr)
msgbox,%theStr%
msgbox,Got string: %theString%
}
```
### 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

+ 47
- 0
examples/simple_example.ahk View File

@ -0,0 +1,47 @@
#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()
; just for convenience, you also could use the "magic numbers" directly
global oscTypeNone := 1
global oscTypeInt := 2
global oscTypeFloat := 4
global oscTypeString := 8
global oscTypeAll := 0xffffffff
; Load DLL and open network port for OSC (7001)
DllCall("LoadLibrary", "Str", "OSC2AHK.dll", "Ptr")
DllCall("OSC2AHK.dll\open", UInt, hWnd, UInt, 7001)
; Tell the DLL to post a system message with ID 0x1001 when a OSC message with address "/test1" and type Integer or Float is received.
DllCall("OSC2AHK.dll\addListener", AStr, "/test1", UInt, 0x1001, UInt, oscTypeInt+oscTypeFloat)
; Tell AHK to invoke the function msghandlerTest1 when a windows message with ID 0x1001 is received
OnMessage(0x1001, "msghandlerTest1")
; This function effectively is called for each OSC message as specified above
msghandlerTest1(oscType, data, msgID, hwnd)
{
; Check which datatype we received (above we accepted Float and Int)
if (oscType = oscTypeInt)
{
; Integers can be used "as is"
msgbox,Got Integer: %data%
}
if (oscType = oscTypeFloat)
{
; Floats have to be "reinterpreted"
VarSetCapacity(buf, 4, 0)
NumPut(data, buf)
theFloat := NumGet(buf, "Float")
msgbox,Got Float: %theFloat%
}
}
; Shutdown the script with Shift+ESC
+Esc::
ExitApp

+ 44
- 0
examples/string_example.ahk View File

@ -0,0 +1,44 @@
#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()
; just for convenience, you also could use the "magic numbers" directly
global oscTypeNone := 1
global oscTypeInt := 2
global oscTypeFloat := 4
global oscTypeString := 8
global oscTypeAll := 0xffffffff
; Load DLL and open network port for OSC (7001)
DllCall("LoadLibrary", "Str", "OSC2AHK.dll", "Ptr")
DllCall("OSC2AHK.dll\open", UInt, hWnd, UInt, 7001)
; Tell the DLL to post a system message with ID 0x1001 when a OSC message with address "/test1" and type string is received.
DllCall("OSC2AHK.dll\addListener", AStr, "/test1", UInt, 0x1001, UInt, oscTypeString)
; Tell AHK to invoke the function msghandlerTest1 when a windows message with ID 0x1001 is received
OnMessage(0x1001, "msghandlerTest1")
; This function effectively is called for each OSC message as specified above
msghandlerTest1(oscType, data, msgID, hwnd)
{
; Check that we really received a string message (kind of redundant for this example)
if (oscType = oscTypeString)
{
; String cannot be passed via system messages,
; so the string is buffered in the DLL and we only get a unique identifier in the "data" variable.
; To get the actual string, we use the getStringData() DLL-call
VarSetCapacity(theString, 20) ; prepare variable to store string in with sufficient size
theString := DllCall("OSC2AHK.dll\getStringData", str, theString, UInt, 20, UInt, data, AStr)
msgbox,Got string: %theString%
}
}
; Shutdown the script with Shift+ESC
+Esc::
ExitApp

+ 40
- 6
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")
@ -48,7 +50,7 @@ msghandlerString(wParam, lParam, msg, hwnd) {
stdout.Write("msghandlerString: ")
VarSetCapacity(theStr, 20)
theStr := DllCall("OSC2AHK.dll\getStringData", AStr, theStr, UInt, 20, UInt, lParam, "Cdecl AStr")
theStr := DllCall("OSC2AHK.dll\getStringData", str, theStr, UInt, 20, UInt, lParam, AStr)
stdout.WriteLine(theStr)
stdout.Close()
@ -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)

+ 2
- 2
oscpack/osc/OscOutboundPacketStream.cpp View File

@ -362,7 +362,7 @@ OutboundPacketStream& OutboundPacketStream::operator<<( const BeginMessage& rhs
//std::strcpy( messageCursor_, rhs.addressPattern );
std::size_t rhsLength = std::strlen(rhs.addressPattern);
strcpy_s(messageCursor_, rhsLength, rhs.addressPattern);
strcpy_s(messageCursor_, rhsLength+1, rhs.addressPattern); //Size check is done manually earlier
messageCursor_ += rhsLength + 1;
// zero pad to 4-byte boundary
@ -605,7 +605,7 @@ OutboundPacketStream& OutboundPacketStream::operator<<( const char *rhs )
*(--typeTagsCurrent_) = STRING_TYPE_TAG;
//std::strcpy( argumentCurrent_, rhs );
std::size_t rhsLength = std::strlen(rhs);
strcpy_s(argumentCurrent_, rhsLength, rhs);
strcpy_s(argumentCurrent_, rhsLength+1, rhs);
argumentCurrent_ += rhsLength + 1;
// zero pad to 4-byte boundary


Loading…
Cancel
Save