{"id":4511,"date":"2023-08-09T17:55:21","date_gmt":"2023-08-09T08:55:21","guid":{"rendered":"https:\/\/blog.criware.com\/?p=4511"},"modified":"2024-05-17T18:47:52","modified_gmt":"2024-05-17T09:47:52","slug":"robot-api-and-external-tools","status":"publish","type":"post","link":"https:\/\/blog.criware.com\/index.php\/2023\/08\/09\/robot-api-and-external-tools\/","title":{"rendered":"Robot API and external tools"},"content":{"rendered":"<p>We conclude our <u>introduction to the CRI <\/u><u>Robot API<\/u> with a script that will not only create new assets in Atom Craft but also communicate with another creative tool, demonstrating how you can easily implement brand new audio workflows!<\/p>\n<p>Indeed, we will render procedural audio patches designed in <a href=\"http:\/\/tsugi-studio.com\/web\/en\/products-gamesynth.html\">GameSynth<\/a> directly in Atom Craft as Materials, and automatically create all the objects needed to play them back.<\/p>\n<div class=\"wp-video\" style=\"width: 800px; display: block; margin: 40px auto;\">\n<div style=\"width: 800px;\" class=\"wp-video\"><video class=\"wp-video-shortcode\" id=\"video-4511-1\" width=\"800\" height=\"423\" preload=\"metadata\" controls=\"controls\"><source type=\"video\/mp4\" src=\"https:\/\/blog.criware.com\/wp-content\/uploads\/2023\/08\/Video-Robot-GSAPI.mp4?_=1\" \/><a href=\"https:\/\/blog.criware.com\/wp-content\/uploads\/2023\/08\/Video-Robot-GSAPI.mp4\">https:\/\/blog.criware.com\/wp-content\/uploads\/2023\/08\/Video-Robot-GSAPI.mp4<\/a><\/video><\/div>\n<\/div>\n<h2 style=\"font-size: 150%; font-weight: bold; margin-top: 40px;\">Connecting to GameSynth<\/h2>\n<p><a href=\"http:\/\/tsugi-studio.com\/web\/en\/products-gamesynth.html\">GameSynth<\/a> is a procedural sound design tool. Its specialized synthesizers (for whooshes, impacts, weather&#8230;) and its modular patching environment make it possible to generate any sound effect needed for game production.<\/p>\n<p>The GameSynth Tool API allows for the remote control of GameSynth via the transmission control protocol (TCP). To create a TCP connection in Python, we simply need to import the \u201csocket\u201d module.<\/p>\n<ul>\n<li>The script starts by setting the address family and the socket type.<\/li>\n<li>The \u201cconnect\u201d function creates the connection to the GameSynth tool (which must be started beforehand). The IP address is currently fixed to the local machine (127.0.0.1) and the port number is 28542 by default.<\/li>\n<li>We make sure to intercept any exception triggered by a connection failure with try \/ except.<\/li>\n<\/ul>\n<pre><code>\r\n# --Description: Use the GameSynth Tool API to render patch in AtomCraft project\r\nimport sys\r\nimport os\r\nimport cri.atomcraft.debug as acdebug\r\nimport cri.atomcraft.project as acproject\r\nimport socket\r\n\r\n#Connect to the GameSynth Tool API\r\ntry:\r\n  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #Set address family and socket type\r\n  s.connect((\"localhost\", 28542)) #Connect to GSAPI\r\n  acdebug.log(\"*** Connected to the GameSynth Tool API ***\")\r\n\r\nexcept ConnectionRefusedError:\r\n  acdebug.warning(\"Could not connect to GameSynth, please start GameSynth first.\")\r\n  sys.exit()\r\n<\/code><\/pre>\n<p>Once the connection established, we can send command messages to GameSynth. \u00a0All the GameSynth commands can be found on the <a href=\"http:\/\/www.tsugi-studio.com\/Software\/GameSynth\/API\/en\/commands.html\">API\u2019s website<\/a>.<\/p>\n<p>In this case, the first thing we want to do is to retrieve the name of the patch that is currently open in GameSynth. This name will be used to create some of the necessary objects in Atom Craft (Materials, Cue, Tracks&#8230;).<\/p>\n<ul>\n<li>The command \u201cget_patchname\u201d is sent using the \u201csend\u201d function.<\/li>\n<li>It will return the name of the patch, that we catch with the \u201crecv\u201d function.<\/li>\n<li>The information returned includes a delimiting character (by default the carriage return) that should be removed from the messages with the \u201cstrip\u201d function.<\/li>\n<\/ul>\n<pre><code>\r\n#Get the name of the current GameSynth patch\r\ns.send(\"get_patchname\".encode('utf-8')) #Send the command to GameSynth\r\ngs_patch_name = s.recv(28542).decode('utf-8') #Receive the result\r\ngs_patch_name = gs_patch_name.rstrip(\"\\r\") #Remove the delimiter\r\n<\/code><\/pre>\n<p>We are now able to connect to the GameSynth Tool API from Atom Craft, send a command, and retrieve some information. The code above will work whenever you want to connect to other tools via TCP, simply change the address \/ port as necessary.<\/p>\n<h2 style=\"font-size: 150%; font-weight: bold; margin-top: 40px;\">Determining the rendering folder<\/h2>\n<p>Before rendering our patch in Atom Craft we need to retrieve the path of the project and the work units to determine the path of the Materials Folder.<\/p>\n<ul>\n<li>The \u201cfind_objects\u201d function from the Robot API is called to get the current project.<\/li>\n<li>Then, with the \u201cget_value\u201d function, the \u201cProjectFilePath\u201d is retrieved.<\/li>\n<li>Finally, the \u201cpath.dirname\u201d function from the OS gives us the absolute path of the project.<\/li>\n<\/ul>\n<pre><code>\r\n# Find the current project and its absolute path\r\ncurrent_project = acproject.find_objects(None, \"Project\")[\"data\"]\r\nproject_path = acproject.get_value(current_project[0], \"ProjectFilePath\")[\"data\"]\r\nproject_path_root = os.path.dirname(project_path)\r\n<\/code><\/pre>\n<p>For the path of the WorkUnits, we need to find what objects are selected in the WorkUnit tree.<\/p>\n<ul>\n<li>First, a list of objects that must be selected to access WorkUnit is built (valid objects are WorkUnits, CueSheets, Cues and Tracks).<\/li>\n<li>Then a loop tests which object is currently selected.<\/li>\n<li>The code varies a bit whether the selected object is a WorkUnit or not, but in both cases the \u201cget_object_path\u201d function is used.<\/li>\n<li>If the selected object is not in the list, the script is aborted.<\/li>\n<\/ul>\n<pre><code>\r\n# Find the WorkUnits and its path from selection and check if the valid object is selected\r\nobject_types = [\"WorkUnit\", \"CueSheet\", \"Cue\", \"Track\"]\r\n\r\nfor object_type in object_types:\r\n  selected_objects = acproject.get_selected_objects(object_type)[\"data\"]\r\n\r\n  if object_type == \"WorkUnit\" and selected_objects:    \r\n    selected_workunits = acproject.get_selected_objects(\"WorkUnit\")[\"data\"]\r\n    workunit_path = acproject.get_object_path(selected_workunits[0])[\"data\"]\r\n    break\r\n\r\n  elif object_type in [\"CueSheet\", \"Cue\", \"Track\"] and selected_objects:\r\n    selected_workunit = acproject.get_parent(selected_objects[0], \"WorkUnit\")[\"data\"]\r\n    selected_workunits = []\r\n    selected_workunits.append(selected_workunit)\r\n    workunit_path = acproject.get_object_path(selected_workunit)[\"data\"]\r\n    break\r\n\r\nelse:\r\n  acdebug.warning(f\"Select one of those objects: {object_types}.\")\r\n  s.close()\r\n  acdebug.log(\"*** Disconnected from the GameSynth Tool API ***\")\r\n  sys.exit()\r\n<\/code><\/pre>\n<p>Once both the project and the WorkUnit paths are known, the Materials folder path can be retrieved, and the GameSynth folder in which our files will be rendered can be created.<\/p>\n<ul>\n<li>The Materials folder path is built via a simple concatenation.<\/li>\n<li>If it doesn\u2019t already exist, the \u201cos.makedirs\u201d function is called to create it under the Materials folder.<\/li>\n<\/ul>\n<pre><code>\r\n#Get the absolute Material path\r\nmaterials_path = project_path_root + workunit_path + \"\/Materials\"\r\n\r\n#Create the GameSynth folder to render the patch if it doesn't already exist\r\ngs_path = materials_path + \"\/GameSynth\"\r\nif not os.path.exists(gs_path):\r\n  os.makedirs(gs_path)\r\n  acdebug.log(\"GameSynth folder successfully created!\")\r\nelse:\r\n  acdebug.log(\"The GameSynth folder already exists.\")\r\n<\/code><\/pre>\n<p>The path of a rendered wave file will be:<\/p>\n<pre><code>\r\ngs_wav_path = gs_path + \"\/\" + gs_patch_name + \"_\" + str(file_index) + \".wav\"\r\n<\/code><\/pre>\n<h2 style=\"font-size: 150%; font-weight: bold; margin-top: 40px;\">Rendering the GameSynth patch<\/h2>\n<p>GameSynth only requires a single command with four arguments to render a patch:<\/p>\n<pre><code>\r\nrender_patch path bits channels duration\r\nrender_patch gs_wav_path 16 1 0\r\n<\/code><\/pre>\n<p>In our final script, we will use User Variables (see our <a href=\"https:\/\/blog.criware.com\/index.php\/2023\/07\/12\/batch-renaming-with-robot-api\/\">previous blog<\/a>) to select the bit depth, number of channels, and duration arguments.<\/p>\n<p>The duration will only be used if the GameSynth patch is infinite, which is determined by calling the \u201cis_infinite\u201d command of the GameSynth API.<br \/>\nThen, \u00a0if the patch is not infinite or a duration has been set, the rendering command can be sent to GameSynth.<\/p>\n<pre><code>\r\n#Check if the patch is infinite\r\ns.send(\"is_infinite\".encode('utf-8'))\r\nis_infinite_result = s.recv(28542).decode('utf-8')\r\nis_infinite_result = int(str(is_infinite_result).replace(\"\\r\", \"\"))\r\n\r\nif ((is_infinite_result == 0) or (is_infinite_result == 1 and DURATION > 0)):\r\n  gsapi_render_patch = f\"render_patch \\\"{gs_wav_path}\\\" {BITDEPTH} {CHANNELS} {DURATION}\"\r\n  s.send(gsapi_render_patch.encode('utf-8'))\r\n    \r\nelse:\r\n  acdebug.warning(\"The patch is infinite, please set a duration higher than 0.\")\r\n  s.close()\r\n  acdebug.log(\"*** Disconnected from the GameSynth Tool API ***\")\r\n  sys.exit()\r\n<\/code><\/pre>\n<h2 style=\"font-size: 150%; font-weight: bold; margin-top: 40px;\">Creating Objects in AtomCraft<\/h2>\n<p>We can now start creating the required objects in Atom Craft. First the rendered files must be registered as Materials.<\/p>\n<ul>\n<li>The Material Root folder is determined by calling the \u201cget_material_rootfolder\u201d function.<\/li>\n<li>Then, the \u201cregister_unregistered_materials\u201d function can be used on the root folder to automatically register the new wave files.<\/li>\n<li>From the root folder, \u201cget_child_object\u201d is used to create a list containing the names of all the Materials we just registered (it will be used later to create our Waveform Regions).<\/li>\n<li>Finally, the \u201csync_local_files\u201d function is used on the GameSynth folder to update the Materials in case they already existed.<\/li>\n<\/ul>\n<pre><code>\r\n#Build the lists of Materials and Material folders\r\nmaterial_folder = acproject.get_child_object(material_root_folder, \"MaterialSubFolder\", \"GameSynth\")[\"data\"]\r\n\r\nbase_name = gs_patch_name + \"_\" + str(file_index) #Get the name of the rendered patch\r\nmaterial = acproject.get_child_object(material_folder, \"Material\", base_name + \".wav\")[\"data\"]\r\nmaterial_list.append(material)\r\n\r\nmaterials_folders_list.append(material_folder)\r\n#Sync with local files to refresh the Materials\r\nacproject.sync_local_files(materials_folders_list)\r\n<\/code><\/pre>\n<p>Once the Materials registered, higher level objects can be created as needed, depending on what was initially selected in the WorkUnit tree. For instance, if the user selected a WorkUnit before running the script, a whole object hierarchy will need to be created: CueSheet Folder, CueSheet, Cue, Track and Waveform Region. But if the selected object was a Cue, only the Track and Waveform Region will have to be created.<\/p>\n<ul>\n<li>First, the type of the selected object is retrieved by using the \u201cget_type_name\u201d function.<\/li>\n<li>From there, at each level of the hierarchy, we check which objects aren\u2019t selected.<\/li>\n<li>With the \u201cget_child_object\u201d function we also check beforehand that the object we want to create doesn\u2019t already exist, to avoid a conflict.<\/li>\n<li>If the object doesn\u2019t exist, the \u201ccreate_object\u201d function is called.<\/li>\n<\/ul>\n<p>Here is the code for the two first levels of the hierarchy (WorkUnit and CueSheet):<\/p>\n<pre><code>\r\n#Create AtomCraft objects based on the selected object\r\nselected_object_type = acproject.get_type_name(selected_objects[0]) [\"data\"]\r\n\r\n#If a WorkUnit is selected\r\nif selected_object_type not in [\"CueSheet\", \"Cue\", \"Track\"]:\r\n  # ----- Creating Cue Sheet\/Cue -----\r\n  # Get the Cue Sheet Root folder\r\n  cuesheet_rootfolder = acproject.get_cuesheet_rootfolder(selected_workunits[0])[\"data\"]\r\n\r\n  # Create a Cue Sheet Root folder named \"GameSynth\" if it does not exist yet\r\n  cuesheet_folder = acproject.get_child_object(cuesheet_rootfolder, \"CueSheetFolder\", \"GameSynth\")[\"data\"]\r\n  if not cuesheet_folder:\r\n    cuesheet_folder = acproject.create_object(cuesheet_rootfolder, \"CueSheetFolder\", \"GameSynth\")[\"data\"]\r\n\r\n  # Create a Cue Sheet in the Cue Sheet folder if it does not exist yet\r\n  cuesheet = acproject.get_child_object(cuesheet_folder, \"CueSheet\", \"GameSynth\")[\"data\"]\r\n  if not cuesheet:\r\n    cuesheet = acproject.create_object(cuesheet_folder, \"CueSheet\", \"GameSynth\")[\"data\"]\r\n\r\n#If a CueSheet is selected\r\nif selected_object_type not in [\"Cue\", \"Track\"]:\r\n  # ----- Creating Cue -----\r\n  if selected_object_type == \"CueSheet\":\r\n    cuesheet = selected_objects[0]\r\n\r\n  # Create a Cue unless it already exists\r\n  cue = acproject.get_child_object(cuesheet, \"Cue\", gs_patch_name)[\"data\"]\r\n  if not cue:\r\n    cue = acproject.create_object(cuesheet, \"Cue\", gs_patch_name)[\"data\"]\r\n<\/code><\/pre>\n<p>The code continues in a similar fashion for the Cue and Track levels.<\/p>\n<p>Note that in the final script, we also added a \u201cVariations\u201d User Variable to render several sound variations at once, which is why the code rendering the sounds differs a bit. Indeed, when rendering variations, a Random No Repeat Cue is created instead of a Polyphonic Cue. And when a Track is selected, a Random No Repeat SubSequence is added instead of a WaveForm Region.<\/p>\n<p>Download the script below to check everything by yourself, and expand it as you need!<\/p>\n<div style=\"max-width: 800px; margin: 0 auto; margin-bottom: 20px; text-align: center;\"><a style=\"display: block; border: 1px solid #ccc; padding: 20px; max-width: 100%; margin: 0 auto;\" href=\"https:\/\/blog.criware.com\/wp-content\/uploads\/2023\/08\/AtomCraft_Robot_PythonScript_GSAPI.zip\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1813\" style=\"display: block; margin: 0 auto;\" src=\"https:\/\/blog.criware.com\/wp-content\/uploads\/2018\/06\/zip.png\" alt=\"zip\" width=\"80\" height=\"78\">AtomCraft_Robot_PythonScript_GSAPI.zip<\/a><\/div>\n","protected":false},"excerpt":{"rendered":"<p>We conclude our introduction to the CRI Robot API with a script that will not only create new assets in<\/p>\n","protected":false},"author":2,"featured_media":4524,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"colormag_page_container_layout":"default_layout","colormag_page_sidebar_layout":"default_layout","footnotes":""},"categories":[5,7],"tags":[],"class_list":["post-4511","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-adx","category-tutorials"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/blog.criware.com\/index.php\/wp-json\/wp\/v2\/posts\/4511","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.criware.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.criware.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.criware.com\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.criware.com\/index.php\/wp-json\/wp\/v2\/comments?post=4511"}],"version-history":[{"count":8,"href":"https:\/\/blog.criware.com\/index.php\/wp-json\/wp\/v2\/posts\/4511\/revisions"}],"predecessor-version":[{"id":5406,"href":"https:\/\/blog.criware.com\/index.php\/wp-json\/wp\/v2\/posts\/4511\/revisions\/5406"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.criware.com\/index.php\/wp-json\/wp\/v2\/media\/4524"}],"wp:attachment":[{"href":"https:\/\/blog.criware.com\/index.php\/wp-json\/wp\/v2\/media?parent=4511"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.criware.com\/index.php\/wp-json\/wp\/v2\/categories?post=4511"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.criware.com\/index.php\/wp-json\/wp\/v2\/tags?post=4511"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}