10 Commits

Author SHA1 Message Date
4b64ae95fd add -m flag for launching macrobuttons app.
add -s flag for launching streamerview app

add -m, -s flags to Use section in README.
2024-07-02 17:49:51 +01:00
f8d2f80cbf remove the extra call to version 2024-07-02 14:49:42 +01:00
4b79b7f849 add -= example to README 2024-07-02 12:00:16 +01:00
3ec98ea391 add note about += and -= to README
add increment/decrement instructions to example_commands
2024-07-02 11:55:34 +01:00
faad5bc2c8 add doc comments 2024-07-02 11:15:17 +01:00
cc0ec73ef4 fix help dialogue 2024-07-02 10:25:25 +01:00
c45df11286 add -v flag to README use section 2024-07-02 10:24:07 +01:00
a383aaa36b reword
add link defs
2024-07-02 10:21:37 +01:00
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
6 changed files with 201 additions and 85 deletions

View File

@@ -13,28 +13,31 @@
## `Use` ## `Use`
```powershell ```powershell
./vmrcli.exe [-h] [-i] [-k] [-D] <api commands> ./vmrcli.exe [-h] [-i] [-k] [-D] [-v] [-m] [-s] <api commands>
``` ```
Where: Where:
- `h`: Prints the help dialogue. - `h`: Prints the help message.
- `i`: Enable interactive mode. If set, any api commands passed on the command line will be ignored. - `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. - `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 - `D`: Set log level 0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=FATAL
- `v`: Enable extra console output (toggle, set messages)
- `m`: Launch the MacroButtons application
- `s`: Launch the StreamerView application
## `API Commands` ## `API Commands`
- Commands starting with `!` will be toggled, use it with boolean parameters. - Commands starting with `!` will be toggled, use it with boolean parameters.
- Commands containing `=` will set a value. - Commands containing `=` will set a value. (use `+=` and `-=` to increment/decrement)
- All other commands with get a value. - All other commands with get a value.
Examples: Examples:
Launch basic GUI, set log level to INFO, Toggle Strip 0 Mute, then print its new value Launch basic GUI, set log level to INFO, Toggle Strip 0 Mute, print its new value, then decrease Bus 0 Gain by 3.8
```powershell ```powershell
./vmrcli.exe -kbasic -D2 !strip[0].mute strip[0].mute ./vmrcli.exe -kbasic -D2 !strip[0].mute strip[0].mute bus[0].gain-=3.8
``` ```
Launch banana GUI, set log level to DEBUG, set Strip 0 label to podmic then print Strip 2 label Launch banana GUI, set log level to DEBUG, set Strip 0 label to podmic then print Strip 2 label
@@ -68,18 +71,25 @@ Scripts can be loaded from text files, for example in Powershell:
./vmrcli.exe -D1 $(Get-Content .\example_commands.txt) ./vmrcli.exe -D1 $(Get-Content .\example_commands.txt)
``` ```
Multiple API commands can be in a single line but they should be space separated.
## `Build` ## `Build`
Run the included `makefile` with [GNU Make](https://www.gnu.org/software/make/). 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: The binary in [Releases][releases] is compiled with coloured logging enabled. To disable this you can override the `LOG_USE_COLOR` variable, for example:
`make LOG_USE_COLOR=no` `make LOG_USE_COLOR=no`
## `Official Documentation` ## `Official Documentation`
- [Voicemeeter Remote C API](https://github.com/onyx-and-iris/Voicemeeter-SDK/blob/main/VoicemeeterRemoteAPI.pdf) - [Voicemeeter Remote C API][remoteapi-docs]
## `Special Thanks` ## `Special Thanks`
- [rxi](https://github.com/rxi) for writing the [log.c](https://github.com/rxi/log.c) package - [rxi][rxi-user] for writing the [log.c][log-c] package
[releases]: https://github.com/onyx-and-iris/vmrcli/releases
[remoteapi-docs]: https://github.com/onyx-and-iris/Voicemeeter-SDK/blob/main/VoicemeeterRemoteAPI.pdf
[rxi-user]: https://github.com/rxi
[log-c]: https://github.com/rxi/log.c

View File

@@ -1,8 +1,5 @@
strip[0].mute strip[0].mute !strip[0].mute strip[0].mute strip[0].gain strip[0].label=podmic strip[0].label
!strip[0].mute strip[1].mute=1 strip[1].mute strip[1].limit-=8
strip[0].mute strip[2].gain-=5 strip[2].comp+=4.8
strip[1].mute=1 bus[0].label
strip[1].mute bus[1].gain-=5.8
strip[0].gain
strip[0].label=podmic
strip[0].label

View File

@@ -12,25 +12,27 @@ enum kind
BASICX64, BASICX64,
BANANAX64, BANANAX64,
POTATOX64, POTATOX64,
MACROBUTTONS = 11,
STREAMERVIEW
}; };
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, unsigned short *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

@@ -3,6 +3,11 @@
#include "vmr.h" #include "vmr.h"
#include "util.h" #include "util.h"
/**
* @brief Removes the last part of a path
*
* @param szPath
*/
void remove_name_in_path(char *szPath) void remove_name_in_path(char *szPath)
{ {
char *p = szPath; char *p = szPath;
@@ -57,6 +62,14 @@ int replace_multiple_space_with_one(char *s, size_t len)
return j; return j;
} }
/**
* @brief
*
* @param s Pointer to a character buffer
* @param kind The kind of Voicemeeter.
* @param n maximum number of characters to be written to the buffer
* @return char* The kind of Voicemeeter as a string
*/
char *kind_as_string(char *s, enum kind kind, int n) char *kind_as_string(char *s, enum kind kind, int n)
{ {
char *kinds[] = { char *kinds[] = {

View File

@@ -8,6 +8,15 @@
#define VERSION_STR_LEN 128 #define VERSION_STR_LEN 128
#define KIND_STR_LEN 64 #define KIND_STR_LEN 64
/**
* @brief Logs into the API.
* Tests for valid connection for up to 2 seconds.
* If successful initializes the dirty parameters.
*
* @param vmr
* @param kind
* @return long
*/
long login(T_VBVMR_INTERFACE *vmr, int kind) long login(T_VBVMR_INTERFACE *vmr, int kind)
{ {
int rep; int rep;
@@ -21,30 +30,37 @@ long login(T_VBVMR_INTERFACE *vmr, int kind)
log_info( log_info(
"Launching Voicemeeter %s GUI", "Launching Voicemeeter %s GUI",
kind_as_string(kind_s, kind, KIND_STR_LEN)); kind_as_string(kind_s, kind, KIND_STR_LEN));
}
time_t endwait;
int timeout = 2; int timeout = 2;
time_t start = time(NULL);
endwait = time(NULL) + timeout;
do do
{ {
if ((rep = version(vmr, &v)) == 0) if ((rep = version(vmr, &v)) == 0)
break;
Sleep(50);
} while (time(NULL) < endwait);
}
if (rep == 0)
{ {
version(vmr, &v);
char version_s[VERSION_STR_LEN]; char version_s[VERSION_STR_LEN];
log_info( log_info(
"Successfully logged into the Voicemeeter API v%s", "Successfully logged into the Voicemeeter API v%s",
version_as_string(version_s, v, VERSION_STR_LEN)); version_as_string(version_s, v, VERSION_STR_LEN));
break;
}
Sleep(50);
} while (time(NULL) < start + timeout);
if (rep == 0)
{
clear_dirty(vmr); clear_dirty(vmr);
} }
return rep; return rep;
} }
/**
* @brief Logs out of the API giving a short wait to allow a
* final instruction to complete.
*
* @param vmr The API interface as a struct
* @return long VBVMR_Logout return value
*/
long logout(T_VBVMR_INTERFACE *vmr) long logout(T_VBVMR_INTERFACE *vmr)
{ {
int rep; int rep;

View File

@@ -9,12 +9,21 @@
#define MAX_LINE 512 #define MAX_LINE 512
/**
* @brief An enum used to define the kind of value
* a 'get' call returns.
*
*/
enum enum
{ {
FLOAT_T, FLOAT_T,
STRING_T, STRING_T,
}; };
/**
* @brief A struct holding the result of a get call.
*
*/
struct result struct result
{ {
int type; int type;
@@ -29,12 +38,17 @@ void help(void);
enum kind 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);
void 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;
bool mflag = false;
bool sflag = false;
int opt; int opt;
char *kvalue = ""; char *kvalue = "";
int dvalue; int dvalue;
@@ -48,20 +62,26 @@ int main(int argc, char *argv[])
log_set_level(LOG_WARN); log_set_level(LOG_WARN);
while ((opt = getopt(argc, argv, "k:ihD:")) != -1) while ((opt = getopt(argc, argv, "hk:msiD:v")) != -1)
{ {
switch (opt) switch (opt)
{ {
case 'i': case 'h':
iflag = true; help();
break; exit(EXIT_SUCCESS);
case 'k': case 'k':
kvalue = optarg; kvalue = optarg;
kind = set_kind(kvalue); kind = set_kind(kvalue);
break; break;
case 'h': case 'm':
help(); mflag = true;
exit(EXIT_SUCCESS); break;
case 's':
sflag = true;
break;
case 'i':
iflag = true;
break;
case 'D': case 'D':
dvalue = atoi(optarg); dvalue = atoi(optarg);
if (dvalue >= LOG_TRACE && dvalue <= LOG_FATAL) if (dvalue >= LOG_TRACE && dvalue <= LOG_FATAL)
@@ -75,6 +95,9 @@ int main(int argc, char *argv[])
"Log level will default to LOG_WARN (3).\n"); "Log level will default to LOG_WARN (3).\n");
} }
break; break;
case 'v':
vflag = true;
break;
default: default:
abort(); abort();
} }
@@ -89,6 +112,18 @@ int main(int argc, char *argv[])
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
if (mflag)
{
log_info("MacroButtons app launched");
run_voicemeeter(vmr, MACROBUTTONS);
}
if (sflag)
{
log_info("StreamerView app launched");
run_voicemeeter(vmr, STREAMERVIEW);
}
if (iflag) if (iflag)
{ {
puts("Interactive mode enabled. Enter 'Q' to exit."); puts("Interactive mode enabled. Enter 'Q' to exit.");
@@ -98,7 +133,7 @@ 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]));
} }
} }
@@ -110,18 +145,21 @@ int main(int argc, char *argv[])
} }
/** /**
* @brief prints the help dialogue * @brief prints the help message
* *
*/ */
void help() void help()
{ {
puts( puts(
"Usage: ./vmrcli.exe [-h] [-i] [-k] [-D] <api commands>\n" "Usage: ./vmrcli.exe [-h] [-i] [-k] [-D] [-v] [-m] [-s] <api commands>\n"
"Where: \n" "Where: \n"
"\th: Prints the help dialogue\n" "\th: Prints the help message\n"
"\ti: Enable interactive mode\n" "\ti: Enable interactive mode\n"
"\tk: The kind of Voicemeeter (basic, banana, potato)\n" "\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"); "\tD: Set log level 0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=FATAL\n"
"\tv: Enable extra console output (toggle, set messages)\n"
"\tm: Launch the MacroButtons application\n"
"\tm: Launch the StreamerView application");
} }
/** /**
@@ -160,6 +198,14 @@ enum kind set_kind(char *kval)
} }
} }
/**
* @brief Defines the DLL interface as a struct.
* Logs into the API.
*
* @param vmr The API interface as a struct
* @param kind
* @return int
*/
int init_voicemeeter(T_VBVMR_INTERFACE *vmr, int kind) int init_voicemeeter(T_VBVMR_INTERFACE *vmr, int kind)
{ {
int rep = initialize_dll_interfaces(vmr); int rep = initialize_dll_interfaces(vmr);
@@ -186,11 +232,16 @@ int init_voicemeeter(T_VBVMR_INTERFACE *vmr, int kind)
return 0; return 0;
} }
/**
* @brief Continuously read lines from stdin.
* Break if 'Q' is entered on the interactive prompt.
* Each line is passed to parse_input()
*
* @param vmr The API interface as a struct
*/
void interactive(T_VBVMR_INTERFACE *vmr) void interactive(T_VBVMR_INTERFACE *vmr)
{ {
char input[MAX_LINE], command[MAX_LINE]; char input[MAX_LINE];
char *p = input;
int i;
size_t len; size_t len;
printf(">> "); printf(">> ");
@@ -201,32 +252,42 @@ void interactive(T_VBVMR_INTERFACE *vmr)
if (len == 1 && toupper(input[0]) == 'Q') if (len == 1 && toupper(input[0]) == 'Q')
break; break;
replace_multiple_space_with_one(input, len); parse_input(vmr, input, len);
while (*p)
{
if (isspace(*p))
{
p++;
continue;
}
log_trace("commands still in buffer: %s", p);
i = 0;
while (*p && !isspace(*p))
command[i++] = *p++;
command[i] = '\0';
if (command[0] != '\0')
parse_command(vmr, command);
memset(command, '\0', MAX_LINE);
}
p = input; /* reset pointer */
memset(input, '\0', MAX_LINE); /* reset input buffer */ memset(input, '\0', MAX_LINE); /* reset input buffer */
printf(">> "); printf(">> ");
} }
} }
/**
* @brief Walks through each line split by a space delimiter.
* Each token is passed to parse_command()
*
* @param vmr The API interface as a struct
* @param input Each input line, from stdin or CLI args
* @param len The length of the input line
*/
void parse_input(T_VBVMR_INTERFACE *vmr, char *input, int len)
{
char *token;
replace_multiple_space_with_one(input, len);
token = strtok(input, " ");
while (token != NULL)
{
parse_command(vmr, token);
token = strtok(NULL, " ");
}
}
/**
* @brief Execute each command according to type.
* See command type definitions in:
* https://github.com/onyx-and-iris/vmrcli?tab=readme-ov-file#api-commands
*
* @param vmr The API interface as a struct
* @param command Each token from the input line as its own command string
*/
void parse_command(T_VBVMR_INTERFACE *vmr, char *command) void parse_command(T_VBVMR_INTERFACE *vmr, char *command)
{ {
log_debug("Parsing %s", command); log_debug("Parsing %s", command);
@@ -240,7 +301,13 @@ void parse_command(T_VBVMR_INTERFACE *vmr, char *command)
if (res.type == FLOAT_T) if (res.type == FLOAT_T)
{ {
if (res.val.f == 1 || res.val.f == 0) if (res.val.f == 1 || res.val.f == 0)
{
set_parameter_float(vmr, command, 1 - res.val.f); set_parameter_float(vmr, command, 1 - res.val.f);
if (vflag)
{
printf("Toggling %s\n", command);
}
}
else else
log_warn("%s does not appear to be a boolean parameter", command); log_warn("%s does not appear to be a boolean parameter", command);
} }
@@ -250,6 +317,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 */
{ {
@@ -259,10 +330,10 @@ void parse_command(T_VBVMR_INTERFACE *vmr, char *command)
switch (res.type) switch (res.type)
{ {
case FLOAT_T: case FLOAT_T:
printf("%.1f\n", res.val.f); printf("%s: %.1f\n", command, res.val.f);
break; break;
case STRING_T: case STRING_T:
printf("%ls\n", res.val.s); printf("%s: %ls\n", command, res.val.s);
break; break;
default: default:
break; break;
@@ -270,6 +341,13 @@ void parse_command(T_VBVMR_INTERFACE *vmr, char *command)
} }
} }
/**
* @brief
*
* @param vmr The API interface as a struct
* @param command A parsed 'get' command as a string
* @param res A struct holding the result of the API call.
*/
void 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);