48 Commits

Author SHA1 Message Date
28945b72c5 modify example_commands to show multiple instructions per line
Add note in README about change.
2024-07-02 10:14:25 +01:00
97fc9ca9ce use strtok to walk through the input string
vflag added for extra console output (toggle, set operations)
2024-07-02 10:13:37 +01:00
12a55a52f2 Merge branch 'main' of https://github.com/onyx-and-iris/vmrcli 2024-07-01 07:35:35 +01:00
d4afbfa881 upd heading 2024-07-01 07:35:32 +01:00
ea1f05d323 remove shift one char 2024-07-01 04:43:15 +01:00
900ed23ebf Create LICENSE 2024-07-01 03:46:09 +01:00
815a19210b protect against unsafe gain changes 2024-07-01 03:40:33 +01:00
adaf3a7837 break up long line 2024-06-29 03:08:03 +01:00
1199b31e2c extra X64 kinds added to enum kind
log message updated to reflect new kinds

replace_multiple_space_with_one() now returns new line length

kind_as_string() added to util.c
2024-06-29 03:05:51 +01:00
2740c6c82d move remove_name_in_path() into util.c
add version_as_string() to util.c
2024-06-28 03:21:38 +01:00
0bda368f59 add -h flag to help dialogue
add -h flag to Use section in README

update example markdown in README
2024-06-28 03:04:14 +01:00
49604b874b ensure we don't step past the NUL terminator here. 2024-06-28 01:54:04 +01:00
161b1061c4 remove unnecessary args in log_trace calls
ensure we shift a char after copying next command from input buffer
2024-06-28 01:23:40 +01:00
3c46b3d9f3 Use VBVMR_GetParameterStringW instead of VBVMR_GetParameterStringA
update struct result definition

fixes bug with unknown parameters
2024-06-27 23:26:46 +01:00
accab93fba reword 2024-06-27 23:09:10 +01:00
947abb3c01 clear the input buffer before reading from stdin again 2024-06-27 23:07:29 +01:00
e06a26f87b add Build section to README. 2024-06-27 23:02:34 +01:00
25bf542b46 add conditional override var LOG_USE_COLOR to makefile 2024-06-27 22:57:35 +01:00
50271edd8f add log_trace message while traversing input buffer 2024-06-27 22:56:58 +01:00
41bf1322ac add Interactive Mode section to README. 2024-06-27 22:07:53 +01:00
f88fb9b994 pass len of input to replace_multiple_space_with_one()
add an input prompt to interactive mode
2024-06-27 22:06:15 +01:00
9191a38745 readd comment 2024-06-27 19:20:21 +01:00
1d71f38d39 add utility function replace_multiple_space_with_one()
use it to parse the interactive input
2024-06-27 19:18:28 +01:00
2dda32ead9 up clean target 2024-06-27 10:28:09 +01:00
f60fdb4ed2 rename iVMR to vmr in vmr.c
update get() prototype. no need to return pointer.
2024-06-27 10:06:22 +01:00
6567c2c610 write error messages using log_error 2024-06-27 08:27:49 +01:00
41afc099dc sleep after, set to 50ms 2024-06-27 03:39:52 +01:00
7db10650fb do..while, sleep before first call to version() 2024-06-27 03:31:26 +01:00
06df144374 fix log_trace messages 2024-06-27 03:23:48 +01:00
35ec276979 add login timeout
print the API version to info string
2024-06-27 02:51:27 +01:00
744f0d64df print float values to 1 dp 2024-06-27 02:00:17 +01:00
760924def8 upd README 2024-06-27 01:18:25 +01:00
7b7d4fc2c7 add log trace messages to vmr.c 2024-06-27 01:18:17 +01:00
0b6e0800ce default log level to LOG_WARN
write message to stderr if -D flag out of range
2024-06-27 01:17:59 +01:00
4488a386b8 move includes into header guards 2024-06-27 01:17:09 +01:00
9863ca6dca remove EOF check 2024-06-26 20:17:33 +01:00
cd11b26ad8 add -D flag to help() output 2024-06-26 19:31:08 +01:00
71e06ac646 fix script files example 2024-06-26 19:03:21 +01:00
050a4d9e60 add link to api documentation 2024-06-26 18:37:52 +01:00
694a4dbc65 add ./ 2024-06-26 18:34:05 +01:00
3f8ed17176 reword 2024-06-26 18:30:47 +01:00
1e54903318 add special thanks section to readme 2024-06-26 18:15:46 +01:00
46df232456 add set strip 0 label in example_commands 2024-06-26 17:19:33 +01:00
9b6733a7f7 add example_commands.txt
add Debug flag to Script files example
2024-06-26 17:17:11 +01:00
85a47098e1 add API Commands section to README 2024-06-26 17:13:04 +01:00
276d34feb9 add README 2024-06-26 17:00:54 +01:00
6373d78c52 print help() if only program name passed 2024-06-26 16:58:10 +01:00
7edb4df30e add logging 2024-06-26 16:44:28 +01:00
13 changed files with 656 additions and 134 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Onyx and Iris
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

87
README.md Normal file
View File

@@ -0,0 +1,87 @@
# Voicemeeter Remote Command Line Utility
## `Tested against`
- Basic 1.1.1.1
- Banana 2.1.1.1
- Potato 3.1.1.1
## `Requirements`
- [Voicemeeter](https://voicemeeter.com/)
## `Use`
```powershell
./vmrcli.exe [-h] [-i] [-k] [-D] <api commands>
```
Where:
- `h`: Prints the help dialogue.
- `i`: Enable interactive mode. If set, any api commands passed on the command line will be ignored.
- `k`: The kind of Voicemeeter (basic, banana or potato). Use this to launch the GUI.
- `D`: Set log level 0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=FATAL
## `API Commands`
- Commands starting with `!` will be toggled, use it with boolean parameters.
- Commands containing `=` will set a value.
- All other commands with get a value.
Examples:
Launch basic GUI, set log level to INFO, Toggle Strip 0 Mute, then print its new value
```powershell
./vmrcli.exe -kbasic -D2 !strip[0].mute strip[0].mute
```
Launch banana GUI, set log level to DEBUG, set Strip 0 label to podmic then print Strip 2 label
```powershell
./vmrcli.exe -kbanana -D1 strip[0].label=podmic strip[2].label
```
## `Interactive Mode`
Running the following command in Powershell:
```powershell
./vmrcli.exe -i
```
Will open an interactive prompt:
```powershell
Interactive mode enabled. Enter 'Q' to exit.
>>
```
API commands follow the same rules as listed above. Entering `Q` or `q` will exit the program.
## `Script files`
Scripts can be loaded from text files, for example in Powershell:
```powershell
./vmrcli.exe -D1 $(Get-Content .\example_commands.txt)
```
Multiple API commands can be in a single line but they should be space separated.
## `Build`
Run the included `makefile` with [GNU Make](https://www.gnu.org/software/make/).
By default the log.c module is built with coloured logging enabled. To disable this you can override the `LOG_USE_COLOR` variable, for example:
`make LOG_USE_COLOR=no`
## `Official Documentation`
- [Voicemeeter Remote C API](https://github.com/onyx-and-iris/Voicemeeter-SDK/blob/main/VoicemeeterRemoteAPI.pdf)
## `Special Thanks`
- [rxi](https://github.com/rxi) for writing the [log.c](https://github.com/rxi/log.c) package

3
example_commands.txt Normal file
View File

@@ -0,0 +1,3 @@
strip[0].mute !strip[0].mute strip[0].mute strip[0].gain strip[0].label=podmic strip[0].label
strip[1].mute=1 strip[1].mute
bus[0].label bus[0].gain=-8.3

View File

@@ -1,9 +1,9 @@
#include <windows.h>
#include "VoicemeeterRemote.h"
#ifndef __CDLL_H__ #ifndef __CDLL_H__
#define __CDLL_H__ #define __CDLL_H__
#include <windows.h>
#include "VoicemeeterRemote.h"
long initialize_dll_interfaces(T_VBVMR_INTERFACE *iVMR); long initialize_dll_interfaces(T_VBVMR_INTERFACE *iVMR);
#endif /*__CDLL_H__*/ #endif /*__CDLL_H__*/

58
include/log.h Normal file
View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2020 rxi
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MIT license. See `log.c` for details.
*/
#ifndef LOG_H
#define LOG_H
#include <stdio.h>
#include <stdarg.h>
#include <stdbool.h>
#include <time.h>
#define LOG_VERSION "0.1.0"
typedef struct
{
va_list ap;
const char *fmt;
const char *file;
struct tm *time;
void *udata;
int line;
int level;
} log_Event;
typedef void (*log_LogFn)(log_Event *ev);
typedef void (*log_LockFn)(bool lock, void *udata);
enum
{
LOG_TRACE,
LOG_DEBUG,
LOG_INFO,
LOG_WARN,
LOG_ERROR,
LOG_FATAL
};
#define log_trace(...) log_log(LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__)
#define log_debug(...) log_log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define log_info(...) log_log(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
#define log_warn(...) log_log(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__)
#define log_error(...) log_log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#define log_fatal(...) log_log(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__)
const char *log_level_string(int level);
void log_set_lock(log_LockFn fn, void *udata);
void log_set_level(int level);
void log_set_quiet(bool enable);
int log_add_callback(log_LogFn fn, void *udata, int level);
int log_add_fp(FILE *fp, int level);
void log_log(int level, const char *file, int line, const char *fmt, ...);
#endif

9
include/util.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef __UTIL_H__
#define __UTIL_H__
void remove_name_in_path(char *szPath);
int replace_multiple_space_with_one(char *s, size_t len);
char *kind_as_string(char *s, enum kind kind, int n);
char *version_as_string(char *, long v, int n);
#endif /* __UTIL_H__ */

View File

@@ -1,34 +1,36 @@
#include <stdbool.h>
#include "voicemeeterRemote.h"
#ifndef __VMR_H__ #ifndef __VMR_H__
#define __VMR_H__ #define __VMR_H__
#include <stdbool.h>
#include "voicemeeterRemote.h"
enum kind enum kind
{ {
BASIC = 1, BASIC = 1,
BANANA, BANANA,
POTATO, POTATO,
POTATOX64 = 6 BASICX64,
BANANAX64,
POTATOX64,
}; };
long login(T_VBVMR_INTERFACE *iVMR, int kind); long login(T_VBVMR_INTERFACE *vmr, int kind);
long logout(T_VBVMR_INTERFACE *iVMR); long logout(T_VBVMR_INTERFACE *vmr);
long run_voicemeeter(T_VBVMR_INTERFACE *iVMR, int kind); long run_voicemeeter(T_VBVMR_INTERFACE *vmr, int kind);
long type(T_VBVMR_INTERFACE *iVMR, long *type); long type(T_VBVMR_INTERFACE *vmr, long *type);
long version(T_VBVMR_INTERFACE *iVMR, long *version); long version(T_VBVMR_INTERFACE *vmr, long *version);
bool is_pdirty(T_VBVMR_INTERFACE *iVMR); bool is_pdirty(T_VBVMR_INTERFACE *vmr);
long get_parameter_float(T_VBVMR_INTERFACE *iVMR, char *param, float *f); long get_parameter_float(T_VBVMR_INTERFACE *vmr, char *param, float *f);
long get_parameter_string(T_VBVMR_INTERFACE *iVMR, char *param, char *s); long get_parameter_string(T_VBVMR_INTERFACE *vmr, char *param, unsigned short *s);
long set_parameter_float(T_VBVMR_INTERFACE *iVMR, char *param, float val); long set_parameter_float(T_VBVMR_INTERFACE *vmr, char *param, float val);
long set_parameter_string(T_VBVMR_INTERFACE *iVMR, char *param, char *s); long set_parameter_string(T_VBVMR_INTERFACE *vmr, char *param, char *s);
long set_parameters(T_VBVMR_INTERFACE *iVMR, char *command); long set_parameters(T_VBVMR_INTERFACE *vmr, char *command);
bool is_mdirty(T_VBVMR_INTERFACE *iVMR); bool is_mdirty(T_VBVMR_INTERFACE *vmr);
long macrobutton_getstatus(T_VBVMR_INTERFACE *iVMR, long n, float *val, long mode); long macrobutton_getstatus(T_VBVMR_INTERFACE *vmr, long n, float *val, long mode);
long macrobutton_setstatus(T_VBVMR_INTERFACE *iVMR, long n, float val, long mode); long macrobutton_setstatus(T_VBVMR_INTERFACE *vmr, long n, float val, long mode);
void clear_dirty(T_VBVMR_INTERFACE *iVMR); void clear_dirty(T_VBVMR_INTERFACE *vmr);
#endif #endif /* __VMR_H__ */

View File

@@ -9,7 +9,12 @@ EXE := $(BIN_DIR)/$(program).exe
SRC := $(wildcard $(SRC_DIR)/*.c) SRC := $(wildcard $(SRC_DIR)/*.c)
OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o) OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
CPPFLAGS := -Iinclude -MMD -MP LOG_USE_COLOR ?= yes
ifeq ($(LOG_USE_COLOR), yes)
CPPFLAGS := -Iinclude -MMD -MP -DLOG_USE_COLOR
else
CPPFLAGS := -Iinclude -MMD -MP
endif
CFLAGS = -O -Wall -W -pedantic -ansi -std=c99 CFLAGS = -O -Wall -W -pedantic -ansi -std=c99
LDFLAGS := -Llib LDFLAGS := -Llib
LDLIBS := -lm LDLIBS := -lm
@@ -28,7 +33,6 @@ $(BIN_DIR) $(OBJ_DIR):
pwsh -Command New-Item -Path $@ -ItemType Directory pwsh -Command New-Item -Path $@ -ItemType Directory
clean: clean:
pwsh -Command Remove-Item -Recurse $(EXE) pwsh -Command Remove-Item -Recurse $(EXE), $(OBJ_DIR)
pwsh -Command Remove-Item -Recurse $(OBJ_DIR)
-include $(OBJ:.o=.d) -include $(OBJ:.o=.d)

View File

@@ -1,6 +1,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include "cdll.h" #include "cdll.h"
#include "util.h"
/*******************************************************************************/ /*******************************************************************************/
/** GET VOICEMEETER DIRECTORY **/ /** GET VOICEMEETER DIRECTORY **/
@@ -12,18 +13,6 @@
#define KEY_WOW64_32KEY 0x0200 #define KEY_WOW64_32KEY 0x0200
#endif #endif
void remove_name_in_path(char *szPath)
{
char *p = szPath;
while (*p++)
;
while (p > szPath && *p != '\\')
p--;
if (*p == '\\')
*p = '\0';
}
bool __cdecl registry_get_voicemeeter_folder(char *szDir) bool __cdecl registry_get_voicemeeter_folder(char *szDir)
{ {
char szKey[256]; char szKey[256];

179
src/log.c Normal file
View File

@@ -0,0 +1,179 @@
/*
* Copyright (c) 2020 rxi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "log.h"
#define MAX_CALLBACKS 32
typedef struct
{
log_LogFn fn;
void *udata;
int level;
} Callback;
static struct
{
void *udata;
log_LockFn lock;
int level;
bool quiet;
Callback callbacks[MAX_CALLBACKS];
} L;
static const char *level_strings[] = {
"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"};
#ifdef LOG_USE_COLOR
static const char *level_colors[] = {
"\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m"};
#endif
static void stdout_callback(log_Event *ev)
{
char buf[16];
buf[strftime(buf, sizeof(buf), "%H:%M:%S", ev->time)] = '\0';
#ifdef LOG_USE_COLOR
fprintf(
ev->udata, "%s %s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ",
buf, level_colors[ev->level], level_strings[ev->level],
ev->file, ev->line);
#else
fprintf(
ev->udata, "%s %-5s %s:%d: ",
buf, level_strings[ev->level], ev->file, ev->line);
#endif
vfprintf(ev->udata, ev->fmt, ev->ap);
fprintf(ev->udata, "\n");
fflush(ev->udata);
}
static void file_callback(log_Event *ev)
{
char buf[64];
buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ev->time)] = '\0';
fprintf(
ev->udata, "%s %-5s %s:%d: ",
buf, level_strings[ev->level], ev->file, ev->line);
vfprintf(ev->udata, ev->fmt, ev->ap);
fprintf(ev->udata, "\n");
fflush(ev->udata);
}
static void lock(void)
{
if (L.lock)
{
L.lock(true, L.udata);
}
}
static void unlock(void)
{
if (L.lock)
{
L.lock(false, L.udata);
}
}
const char *log_level_string(int level)
{
return level_strings[level];
}
void log_set_lock(log_LockFn fn, void *udata)
{
L.lock = fn;
L.udata = udata;
}
void log_set_level(int level)
{
L.level = level;
}
void log_set_quiet(bool enable)
{
L.quiet = enable;
}
int log_add_callback(log_LogFn fn, void *udata, int level)
{
for (int i = 0; i < MAX_CALLBACKS; i++)
{
if (!L.callbacks[i].fn)
{
L.callbacks[i] = (Callback){fn, udata, level};
return 0;
}
}
return -1;
}
int log_add_fp(FILE *fp, int level)
{
return log_add_callback(file_callback, fp, level);
}
static void init_event(log_Event *ev, void *udata)
{
if (!ev->time)
{
time_t t = time(NULL);
ev->time = localtime(&t);
}
ev->udata = udata;
}
void log_log(int level, const char *file, int line, const char *fmt, ...)
{
log_Event ev = {
.fmt = fmt,
.file = file,
.line = line,
.level = level,
};
lock();
if (!L.quiet && level >= L.level)
{
init_event(&ev, stderr);
va_start(ev.ap, fmt);
stdout_callback(&ev);
va_end(ev.ap);
}
for (int i = 0; i < MAX_CALLBACKS && L.callbacks[i].fn; i++)
{
Callback *cb = &L.callbacks[i];
if (level >= cb->level)
{
init_event(&ev, cb->udata);
va_start(ev.ap, fmt);
cb->fn(&ev);
va_end(ev.ap);
}
}
unlock();
}

90
src/util.c Normal file
View File

@@ -0,0 +1,90 @@
#include <stddef.h>
#include <stdio.h>
#include "vmr.h"
#include "util.h"
void remove_name_in_path(char *szPath)
{
char *p = szPath;
while (*p++)
;
while (p > szPath && *p != '\\')
p--;
if (*p == '\\')
*p = '\0';
}
/**
* @brief replaces multiple newlines and tabs with single spaces
*
* @param s the string to be reduced
* @param len current length of the string
* @return int new length of the string
*/
int replace_multiple_space_with_one(char *s, size_t len)
{
int j = 0;
int count = 0;
if (len == 1 && (s[0] == ' ' || s[0] == '\t'))
{
s[0] = '\0';
return len;
}
if (len < 2)
return len;
for (int i = 0; s[i] != '\0'; i++)
{
if (s[i] == ' ' || s[i] == '\t')
{
count++;
}
if (s[i] != ' ' && s[i] != '\t')
{
if (count >= 1)
{
count = 0;
s[j++] = ' ';
}
s[j++] = s[i];
}
}
s[j] = '\0';
return j;
}
char *kind_as_string(char *s, enum kind kind, int n)
{
char *kinds[] = {
"Basic",
"Banana",
"Potato",
"Basic x64",
"Banana x64",
"Potato x64",
};
snprintf(s, n, kinds[kind - 1]);
return s;
}
/**
* @brief returns Voicemeeter's version as a string
*
* @param s string buffer the version will be written to
* @param v unprocessed version as a long int
* @param n maximum number of characters to be written to the buffer
* @return char*
*/
char *version_as_string(char *s, long v, int n)
{
long v1 = (v & 0xFF000000) >> 24,
v2 = (v & 0x00FF0000) >> 16,
v3 = (v & 0x0000FF00) >> 8,
v4 = (v & 0x000000FF);
snprintf(s, n, "%i.%i.%i.%i", (int)v1, (int)v2, (int)v3, (int)v4);
return s;
}

122
src/vmr.c
View File

@@ -1,111 +1,133 @@
#include <windows.h> #include <windows.h>
#include <stdio.h> #include <stdio.h>
#include <time.h>
#include "vmr.h" #include "vmr.h"
#include "log.h"
#include "util.h"
long login(T_VBVMR_INTERFACE *iVMR, int kind) #define VERSION_STR_LEN 128
#define KIND_STR_LEN 64
long login(T_VBVMR_INTERFACE *vmr, int kind)
{ {
int rep; int rep;
long v;
rep = iVMR->VBVMR_Login(); rep = vmr->VBVMR_Login();
Sleep(20);
if (rep == 1) if (rep == 1)
{ {
rep = run_voicemeeter(iVMR, kind); run_voicemeeter(vmr, kind);
switch (kind) char kind_s[KIND_STR_LEN];
{ log_info(
case BASIC: "Launching Voicemeeter %s GUI",
puts("Launching Voicemeeter Basic GUI"); kind_as_string(kind_s, kind, KIND_STR_LEN));
break;
case BANANA:
puts("Launching Voicemeeter Banana GUI");
break;
case POTATO:
puts("Launching Voicemeeter Potato GUI");
break;
case POTATOX64:
puts("Launching Voicemeeter Potato x64 GUI");
break;
}
Sleep(1200); time_t endwait;
int timeout = 2;
endwait = time(NULL) + timeout;
do
{
if ((rep = version(vmr, &v)) == 0)
break;
Sleep(50);
} while (time(NULL) < endwait);
} }
if (rep == 0) if (rep == 0)
{ {
puts("Successfully logged into the Voicemeeter API"); version(vmr, &v);
clear_dirty(iVMR); char version_s[VERSION_STR_LEN];
log_info(
"Successfully logged into the Voicemeeter API v%s",
version_as_string(version_s, v, VERSION_STR_LEN));
clear_dirty(vmr);
} }
return rep; return rep;
} }
long logout(T_VBVMR_INTERFACE *iVMR) long logout(T_VBVMR_INTERFACE *vmr)
{ {
int rep;
Sleep(20); /* give time for last command */ Sleep(20); /* give time for last command */
return iVMR->VBVMR_Logout(); rep = vmr->VBVMR_Logout();
if (rep == 0)
log_info("Successfully logged out of the Voicemeeter API");
return rep;
} }
long run_voicemeeter(T_VBVMR_INTERFACE *iVMR, int kind) long run_voicemeeter(T_VBVMR_INTERFACE *vmr, int kind)
{ {
return iVMR->VBVMR_RunVoicemeeter((long)kind); log_trace("VBVMR_RunVoicemeeter(%d)", kind);
return vmr->VBVMR_RunVoicemeeter((long)kind);
} }
long type(T_VBVMR_INTERFACE *iVMR, long *type) long type(T_VBVMR_INTERFACE *vmr, long *type)
{ {
return iVMR->VBVMR_GetVoicemeeterType(type); log_trace("VBVMR_GetVoicemeeterType(<long> *t)");
return vmr->VBVMR_GetVoicemeeterType(type);
} }
long version(T_VBVMR_INTERFACE *iVMR, long *version) long version(T_VBVMR_INTERFACE *vmr, long *version)
{ {
return iVMR->VBVMR_GetVoicemeeterType(version); log_trace("VBVMR_GetVoicemeeterVersion(<long> *v)");
return vmr->VBVMR_GetVoicemeeterVersion(version);
} }
bool is_pdirty(T_VBVMR_INTERFACE *iVMR) bool is_pdirty(T_VBVMR_INTERFACE *vmr)
{ {
return iVMR->VBVMR_IsParametersDirty() == 1; log_trace("VBVMR_IsParametersDirty()");
return vmr->VBVMR_IsParametersDirty() == 1;
} }
long get_parameter_float(T_VBVMR_INTERFACE *iVMR, char *param, float *f) long get_parameter_float(T_VBVMR_INTERFACE *vmr, char *param, float *f)
{ {
return iVMR->VBVMR_GetParameterFloat(param, f); log_trace("VBVMR_GetParameterFloat(%s, <float> *f)", param);
return vmr->VBVMR_GetParameterFloat(param, f);
} }
long get_parameter_string(T_VBVMR_INTERFACE *iVMR, char *param, char *s) long get_parameter_string(T_VBVMR_INTERFACE *vmr, char *param, unsigned short *s)
{ {
return iVMR->VBVMR_GetParameterStringA(param, s); log_trace("VBVMR_GetParameterStringW(%s, <unsigned short> *s)", param);
return vmr->VBVMR_GetParameterStringW(param, s);
} }
long set_parameter_float(T_VBVMR_INTERFACE *iVMR, char *param, float val) long set_parameter_float(T_VBVMR_INTERFACE *vmr, char *param, float val)
{ {
return iVMR->VBVMR_SetParameterFloat(param, val); log_trace("VBVMR_SetParameterFloat(%s, %.1f)", param, val);
return vmr->VBVMR_SetParameterFloat(param, val);
} }
long set_parameter_string(T_VBVMR_INTERFACE *iVMR, char *param, char *s) long set_parameter_string(T_VBVMR_INTERFACE *vmr, char *param, char *s)
{ {
return iVMR->VBVMR_SetParameterStringA(param, s); log_trace("VBVMR_SetParameterStringA(%s, %s)", param, s);
return vmr->VBVMR_SetParameterStringA(param, s);
} }
long set_parameters(T_VBVMR_INTERFACE *iVMR, char *command) long set_parameters(T_VBVMR_INTERFACE *vmr, char *command)
{ {
return iVMR->VBVMR_SetParameters(command); log_trace("VBVMR_SetParameters(%s)", command);
return vmr->VBVMR_SetParameters(command);
} }
bool is_mdirty(T_VBVMR_INTERFACE *iVMR) bool is_mdirty(T_VBVMR_INTERFACE *vmr)
{ {
return iVMR->VBVMR_MacroButton_IsDirty() == 1; return vmr->VBVMR_MacroButton_IsDirty() == 1;
} }
long macrobutton_getstatus(T_VBVMR_INTERFACE *iVMR, long n, float *val, long mode) long macrobutton_getstatus(T_VBVMR_INTERFACE *vmr, long n, float *val, long mode)
{ {
return iVMR->VBVMR_MacroButton_GetStatus(n, val, mode); return vmr->VBVMR_MacroButton_GetStatus(n, val, mode);
} }
long macrobutton_setstatus(T_VBVMR_INTERFACE *iVMR, long n, float val, long mode) long macrobutton_setstatus(T_VBVMR_INTERFACE *vmr, long n, float val, long mode)
{ {
return iVMR->VBVMR_MacroButton_SetStatus(n, val, mode); return vmr->VBVMR_MacroButton_SetStatus(n, val, mode);
} }
void clear_dirty(T_VBVMR_INTERFACE *iVMR) void clear_dirty(T_VBVMR_INTERFACE *vmr)
{ {
Sleep(30); Sleep(30);
while (is_pdirty(iVMR)) while (is_pdirty(vmr))
Sleep(1); Sleep(1);
} }

View File

@@ -4,6 +4,8 @@
#include <getopt.h> #include <getopt.h>
#include "cdll.h" #include "cdll.h"
#include "vmr.h" #include "vmr.h"
#include "log.h"
#include "util.h"
#define MAX_LINE 512 #define MAX_LINE 512
@@ -19,25 +21,37 @@ struct result
union val union val
{ {
float f; float f;
char s[MAX_LINE]; wchar_t s[MAX_LINE];
} val; } val;
}; };
void help(void); void help(void);
int set_kind(char *kval); enum kind set_kind(char *kval);
int init_voicemeeter(T_VBVMR_INTERFACE *vmr, int kind); int init_voicemeeter(T_VBVMR_INTERFACE *vmr, int kind);
void interactive(T_VBVMR_INTERFACE *vmr); void interactive(T_VBVMR_INTERFACE *vmr);
void parse_input(T_VBVMR_INTERFACE *vmr, char *input, int len);
void parse_command(T_VBVMR_INTERFACE *vmr, char *command); void parse_command(T_VBVMR_INTERFACE *vmr, char *command);
struct result *get(T_VBVMR_INTERFACE *vmr, char *command, struct result *res); void get(T_VBVMR_INTERFACE *vmr, char *command, struct result *res);
bool vflag = false;
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
bool iflag = false; bool iflag = false;
int opt; int opt;
char *kvalue = ""; char *kvalue = "";
int kind = BANANA; int dvalue;
enum kind kind = BANANAX64;
while ((opt = getopt(argc, argv, "k:ih")) != -1) if (argc == 1)
{
help();
exit(EXIT_SUCCESS);
}
log_set_level(LOG_WARN);
while ((opt = getopt(argc, argv, "k:ihD:v")) != -1)
{ {
switch (opt) switch (opt)
{ {
@@ -46,10 +60,27 @@ int main(int argc, char *argv[])
break; break;
case 'k': case 'k':
kvalue = optarg; kvalue = optarg;
kind = set_kind(kvalue);
break; break;
case 'h': case 'h':
help(); help();
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
case 'D':
dvalue = atoi(optarg);
if (dvalue >= LOG_TRACE && dvalue <= LOG_FATAL)
{
log_set_level(dvalue);
}
else
{
log_error(
"-D arg out of range, expected value from 0 up to 5\n"
"Log level will default to LOG_WARN (3).\n");
}
break;
case 'v':
vflag = true;
break;
default: default:
abort(); abort();
} }
@@ -57,10 +88,6 @@ int main(int argc, char *argv[])
T_VBVMR_INTERFACE iVMR; T_VBVMR_INTERFACE iVMR;
T_VBVMR_INTERFACE *vmr = &iVMR; T_VBVMR_INTERFACE *vmr = &iVMR;
if (kvalue && kvalue[0] != '\0')
{
kind = set_kind(kvalue);
}
int rep = init_voicemeeter(vmr, kind); int rep = init_voicemeeter(vmr, kind);
if (rep != 0) if (rep != 0)
@@ -77,40 +104,54 @@ int main(int argc, char *argv[])
{ {
for (int i = optind; i < argc; i++) for (int i = optind; i < argc; i++)
{ {
parse_command(vmr, argv[i]); parse_input(vmr, argv[i], strlen(argv[i]));
} }
} }
rep = logout(vmr); rep = logout(vmr);
if (rep == 0) if (rep == 0)
{
puts("Successfully logged out of the Voicemeeter API");
return EXIT_SUCCESS; return EXIT_SUCCESS;
}
else else
{
return EXIT_FAILURE; return EXIT_FAILURE;
}
} }
/**
* @brief prints the help dialogue
*
*/
void help() void help()
{ {
puts( puts(
"Usage: ./vmrcli.exe [-i] [-k] <api commands>\n" "Usage: ./vmrcli.exe [-h] [-i] [-k] [-D] [-v] <api commands>\n"
"Where: \n" "Where: \n"
"\th: Prints the help dialogue\n"
"\ti: Enable interactive mode\n" "\ti: Enable interactive mode\n"
"\tk: The kind of Voicemeeter (basic, banana, potato)"); "\tk: The kind of Voicemeeter (basic, banana, potato)\n"
"\tD: Set log level 0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=FATAL"
"\tv: Enable extra console output (toggle, set messages)\n");
} }
int set_kind(char *kval) /**
* @brief Set the kind object
*
* @param kval
* @return enum kind
*/
enum kind set_kind(char *kval)
{ {
if (strcmp(kval, "basic") == 0) if (strcmp(kval, "basic") == 0)
{ {
return BASIC; if (sizeof(void *) == 8)
return BASICX64;
else
return BASIC;
} }
else if (strcmp(kval, "banana") == 0) else if (strcmp(kval, "banana") == 0)
{ {
return BANANA; if (sizeof(void *) == 8)
return BANANAX64;
else
return BANANA;
} }
else if (strcmp(kval, "potato") == 0) else if (strcmp(kval, "potato") == 0)
{ {
@@ -121,7 +162,7 @@ int set_kind(char *kval)
} }
else else
{ {
fprintf(stderr, "Unknown Voicemeeter kind '%s'\n", kval); log_error("Unknown Voicemeeter kind '%s'\n", kval);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
} }
@@ -133,11 +174,11 @@ int init_voicemeeter(T_VBVMR_INTERFACE *vmr, int kind)
{ {
if (rep == -100) if (rep == -100)
{ {
fputs("Voicemeeter is not installed", stderr); log_error("Voicemeeter is not installed");
} }
else else
{ {
fprintf(stderr, "Error loading Voicemeeter dll with code %d\n", rep); log_error("Error loading Voicemeeter dll with code %d\n", rep);
} }
return rep; return rep;
} }
@@ -145,7 +186,7 @@ int init_voicemeeter(T_VBVMR_INTERFACE *vmr, int kind)
rep = login(vmr, kind); rep = login(vmr, kind);
if (rep != 0) if (rep != 0)
{ {
fputs("Error logging into Voicemeeter", stderr); log_error("Error logging into Voicemeeter");
return rep; return rep;
} }
@@ -155,33 +196,40 @@ int init_voicemeeter(T_VBVMR_INTERFACE *vmr, int kind)
void interactive(T_VBVMR_INTERFACE *vmr) void interactive(T_VBVMR_INTERFACE *vmr)
{ {
char input[MAX_LINE]; char input[MAX_LINE];
char *p = input; size_t len;
printf(">> ");
while (fgets(input, MAX_LINE, stdin) != NULL) while (fgets(input, MAX_LINE, stdin) != NULL)
{ {
if (strlen(input) == 2 && (strncmp(input, "Q", 1) == 0 || strncmp(input, "q", 1) == 0)) input[strcspn(input, "\n")] = 0;
len = strlen(input);
if (len == 1 && toupper(input[0]) == 'Q')
break; break;
while (*p) parse_input(vmr, input, len);
{
char command[MAX_LINE];
int i = 0;
while (!isspace(*p)) memset(input, '\0', MAX_LINE); /* reset input buffer */
command[i++] = *p++; printf(">> ");
command[i] = '\0'; }
p++; /* shift to next char */ }
parse_command(vmr, command); void parse_input(T_VBVMR_INTERFACE *vmr, char *input, int len)
} {
char *token;
p = input; /* reset pointer */ replace_multiple_space_with_one(input, len);
token = strtok(input, " ");
while (token != NULL)
{
parse_command(vmr, token);
token = strtok(NULL, " ");
} }
} }
void parse_command(T_VBVMR_INTERFACE *vmr, char *command) void parse_command(T_VBVMR_INTERFACE *vmr, char *command)
{ {
printf("Parsing %s\n", command); log_debug("Parsing %s", command);
if (command[0] == '!') /* toggle */ if (command[0] == '!') /* toggle */
{ {
command++; command++;
@@ -190,7 +238,16 @@ void parse_command(T_VBVMR_INTERFACE *vmr, char *command)
get(vmr, command, &res); get(vmr, command, &res);
if (res.type == FLOAT_T) if (res.type == FLOAT_T)
{ {
set_parameter_float(vmr, command, 1 - res.val.f); if (res.val.f == 1 || res.val.f == 0)
{
set_parameter_float(vmr, command, 1 - res.val.f);
if (vflag)
{
printf("Toggling %s\n", command);
}
}
else
log_warn("%s does not appear to be a boolean parameter", command);
} }
return; return;
} }
@@ -198,6 +255,10 @@ void parse_command(T_VBVMR_INTERFACE *vmr, char *command)
if (strchr(command, '=') != NULL) /* set */ if (strchr(command, '=') != NULL) /* set */
{ {
set_parameters(vmr, command); set_parameters(vmr, command);
if (vflag)
{
printf("Setting %s\n", command);
}
} }
else /* get */ else /* get */
{ {
@@ -207,19 +268,18 @@ void parse_command(T_VBVMR_INTERFACE *vmr, char *command)
switch (res.type) switch (res.type)
{ {
case FLOAT_T: case FLOAT_T:
printf("%.2f\n", res.val.f); printf("%s: %.1f\n", command, res.val.f);
break; break;
case STRING_T: case STRING_T:
puts(res.val.s); printf("%s: %ls\n", command, res.val.s);
break; break;
default: default:
fputs("Unknown result...", stderr);
break; break;
} }
} }
} }
struct result *get(T_VBVMR_INTERFACE *vmr, char *command, struct result *res) void get(T_VBVMR_INTERFACE *vmr, char *command, struct result *res)
{ {
clear_dirty(vmr); clear_dirty(vmr);
if (get_parameter_float(vmr, command, &res->val.f) != 0) if (get_parameter_float(vmr, command, &res->val.f) != 0)
@@ -228,9 +288,7 @@ struct result *get(T_VBVMR_INTERFACE *vmr, char *command, struct result *res)
if (get_parameter_string(vmr, command, res->val.s) != 0) if (get_parameter_string(vmr, command, res->val.s) != 0)
{ {
res->val.s[0] = 0; res->val.s[0] = 0;
fputs("Unknown parameter", stderr); log_error("Unknown parameter '%s'", command);
} }
} }
return res;
} }