├── .gitignore ├── README.md ├── a8_exporter.py ├── a8_generator.py ├── a8_slicer.py ├── prst002.yml └── semitone_generator.py /.gitignore: -------------------------------------------------------------------------------- 1 | #Ignore wav files used for testing 2 | *.wav 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Assimil8or Helpers 2 | 3 | Scripts I'm using while working with the Rossum-Electro Assimil8or. The idea is to evolve the scripts based on my own workflow with the goal of focusing on music rather than anything else while I'm working on the modular. If I can reduce some of the more tedious and repetitive tasks, then I can hopefully spend creative focus on music rather than configuration. 4 | 5 | # Preset Template 6 | 7 | The prst002.yml file is meant to be an ongoing evolution of a preset template that allows me to quickly dial in the values I like to use when working with the module. This can save some time based on perferred cv mappings etc. The other benefit I realized was the ability to preview samples on a computer, and place them in the template so they are loaded at the module, rather than previewing samples, copying them onto the microsd and turning the knob to select them. Initially this was kind of the only reason I made a repo, to hold templates and keep track of changes...but then see below for what happened... 8 | 9 | # Slicer 10 | 11 | The idea is a simple script that spits out preformatted text that divides a sample into 8 equal lengths (some precision lost from rounding). I created this so that I could more easily divide zones and then fine tune the start and stop settings at the module. It currently outputs the voltage table associated with the XOR NerdSeq that maps C-4 thru G-4 to each Zone 8 thru 1 (in that order). It would be neat if the community would add more voltage tables for their specific sequencers and workflows. To run simply pass the name of the sample and its location like so: 12 | 13 | `python3 a8_slicer.py -s loop001.wav -d /Users/digitalohm/Documents/GitHub/assimil8or-helpers/` 14 | 15 | See --help for more details 16 | 17 | ![alt tag](https://i.imgur.com/T0tSEb5h.png "Slicer Output") 18 | 19 | # Exporter 20 | 21 | This script came about as I was previewing samples in Ableton. I really like the drum rack when working with samples, it allows me to create and preview combinations of samples in a rather rapid way. After doing this, I wanted to be able to export the files for use on the assimil8or. The workflow as of now is to create a drum rack in an empty ableton project, save the project with the name prstXXX (whatever preset I'm making), then collect and save. From there I run the script to copy the files to a new folder with the structure needed for the Generator script. This also trims samples that have filesnames exceeding the 47 character limit. 22 | 23 | To use: 24 | 25 | `python3 a8_exporter.py -p prst007 -s '/Users/digitalohm/Documents/Ableton/prst007 Project/Samples/Imported' -d '/Users/digitalohm/Documents'` 26 | 27 | See --help for for the explaination 28 | 29 | To use you'll need to update the project_dir, source_dir, temp_dir and dest_dir variables. For example when I save my ableton projects that I'm using for this task, I save them as: "prst00X Project" so you can see that I've made the variables to match. If your folder containing the samples is /Users/you/kewlPreset then use that. The project_dir is only for string building so if you update the other 3 it shouldn't matter. 30 | 31 | ![alt tag](https://i.imgur.com/p2FHeZ9h.png "Exporter Output") 32 | 33 | ![alt tag](https://i.imgur.com/aehGq34h.png "Sub Directory Example") 34 | 35 | # Generator 36 | 37 | The generator will create the copy the samples and create the .yml file in a folder that can be copied to the a8. 38 | 39 | To use: execute the script with the -p, -n and -d parameters, like so 40 | 41 | `python3 a8_generator.py -p prst006 -n 6 -d /Users/digitalohm/Documents` 42 | 43 | See --help for the explaination 44 | 45 | ![alt tag](https://i.imgur.com/u6RQ0kVh.png "Generator Sub Dir") 46 | 47 | # Semitone Generator 48 | 49 | This will simply calculate the semitones you can use in order to pitch up or down a sample to a desired bpm. 50 | 51 | To use: execute the script with the -o (orignal bpm) and -t (target bpm) parameters 52 | 53 | `python3 bpm.py -o 120 -t 128` 54 | 55 | # Thanks 56 | 57 | Thanks to Gimber and IOManip for letting me constantly ping ideas about this, workflows and formulas. Many thanks for their corrections :D 58 | 59 | Thanks to Rossum Electro for making kewl modules, and special thanks to Marco at Rossum for putting up with my constant support requests :D 60 | 61 | Most of the code was adapated from various solutions on stackexchange. The developer community is great 62 | -------------------------------------------------------------------------------- /a8_exporter.py: -------------------------------------------------------------------------------- 1 | import os, shutil, glob, argparse 2 | 3 | ## Using the parser for command line arguments 4 | parser = argparse.ArgumentParser() 5 | parser.add_argument('-p', '--preset', required=True, help='This is the name of the preset, and the final destination folder, and prefix for fine renames') 6 | parser.add_argument('-s', '--source_directory', required=True, help='This is the source directory that contains the samples you want to copy') 7 | parser.add_argument('-d', '--destination_directory', required=True, help='This is the place you want to copy the files to') 8 | args = vars(parser.parse_args()) 9 | 10 | ## The project_dir variable is used just for some string manipulation below, it can be ignored if you are using a different method 11 | project_dir = args['preset'] 12 | 13 | ## The source_dir is the place where your samples are at, the temp_dir is a temporary folder for renaming the copied files, and the dest_dir is the final place for them 14 | #source_dir = '/Users/digitalohm/Documents/Ableton/' + project_dir + ' Project/Samples/Imported' 15 | source_dir = args['source_directory'] 16 | temp_dir = args['destination_directory'] + '/temp_samples' 17 | dest_dir = args['destination_directory'] + '/' + project_dir 18 | 19 | ## The sub_dir variable corresponds to the channels on the assimil8or, '1' = Channel 1 etc. 20 | sub_dir = ['1', '2', '3', '4', '5', '6', '7', '8'] 21 | 22 | ## Create directories 23 | if not os.path.exists(temp_dir): 24 | os.mkdir(temp_dir) 25 | 26 | if not os.path.exists(dest_dir): 27 | os.mkdir(dest_dir) 28 | 29 | i = 0 30 | for subdirectory in sub_dir: 31 | if not os.path.exists(dest_dir + '/' + sub_dir[i]): 32 | os.mkdir(dest_dir + '/' + sub_dir[i]) 33 | i += 1 34 | 35 | ## First copy the .wav files from the source_dir to the temp_dir 36 | for root, dirnames, filenames in os.walk(source_dir): 37 | for file in filenames: 38 | (shortname, extension) = os.path.splitext(file) 39 | if extension == '.wav' : 40 | shutil.copy2(os.path.join(root,file), os.path.join(temp_dir, os.path.relpath(os.path.join(root,file),source_dir))) 41 | #print(os.path.join(temp_dir, os.path.relpath(os.path.join(root,file),source_dir))) 42 | 43 | ## Next rename the files in the temp_dir 44 | for filename in os.listdir(temp_dir): 45 | (shortname, extension) = os.path.splitext(filename) 46 | if extension == '.wav' : 47 | prefix = project_dir + '_' 48 | if len(shortname) > 47: 49 | os.rename(os.path.join(temp_dir, filename), os.path.join(temp_dir, prefix + shortname[:-(len(shortname)-47)] + extension)) 50 | #print(prefix + shortname[:-(len(shortname)-47)] + extension) 51 | else: 52 | os.rename(os.path.join(temp_dir, filename), os.path.join(temp_dir, prefix + filename)) 53 | #print(prefix + filename) 54 | 55 | ## Finally copy the renamed files from the temp_dir to the dest_dir 56 | for root, dirnames, filenames in os.walk(temp_dir): 57 | for file in filenames: 58 | (shortname, extension) = os.path.splitext(file) 59 | if extension == '.wav' : 60 | shutil.copy2(os.path.join(root,file), os.path.join(dest_dir, os.path.relpath(os.path.join(root,file),temp_dir))) 61 | #print(temp_dir) 62 | 63 | ## And remove the temp_dir 64 | shutil.rmtree(temp_dir) 65 | -------------------------------------------------------------------------------- /a8_generator.py: -------------------------------------------------------------------------------- 1 | import os, shutil, glob, re, argparse, sys 2 | 3 | ## Using the parser for command line arguments 4 | parser = argparse.ArgumentParser() 5 | parser.add_argument('-p', '--preset', required=True, help='This is the name of the preset') 6 | parser.add_argument('-n', '--number', required=True, help='This is the number of the preset, which is set inside the yml') 7 | parser.add_argument('-d', '--directory', required=True, help='location of the samples') 8 | #Not yet implmented 9 | parser.add_argument("-v", "--verbose", help="prints the yml configuration to stdout", action="store_true") 10 | args = vars(parser.parse_args()) 11 | 12 | ## Voltage table for the nerseq. This is for notes C-4 through G-4 to hit Zones 8 thru 1 (in that order) 13 | vt_nerdseq_001 = ['+4.56', '+4.48', '+4.39', '+4.31', '+4.23', '+4.14', '+4.04', '-5.00'] 14 | 15 | ## This is the name of the preset which works nicely if it's the same name as your sample source folder 16 | preset = args['preset'] 17 | 18 | ## First make sure the path to the samples is valid 19 | if os.path.isdir(args['directory']): 20 | sample_dir = args['directory'] + '/' + preset 21 | 22 | ## String building once for later use 23 | dest_dir = args["directory"] + '/completed_'# + preset 24 | 25 | ## Check if the destination directory exists, if it does, delete it and create it agian. If not, create it! 26 | if not os.path.exists(dest_dir): 27 | os.mkdir(dest_dir) 28 | #else: 29 | # shutil.rmtree(dest_dir) 30 | # os.mkdir(dest_dir) 31 | 32 | ## Build a list of the channel directories that are not empty, and print the beginnings of the yml 33 | channels = [] 34 | file_path = dest_dir + '/' + preset + '.yml' 35 | #print(file_path) 36 | with open(file_path, 'w') as f: 37 | f.write('Preset ' + args['number'] + ':\r\n') 38 | #print('Preset 1 :') 39 | f.write(' Name : ' + sample_dir[len(sample_dir)-7:len(sample_dir)]+'\r\n') 40 | #print(' Name : ' + sample_dir[len(sample_dir)-7:len(sample_dir)]) 41 | ## Loop one time for directories that are not empty 42 | for root, dirnames, filenames in os.walk(sample_dir): 43 | for directory in dirnames: 44 | if os.listdir(sample_dir + '/' + directory): 45 | channels.append(directory) 46 | 47 | ## Next we loop through each folder from the channles[] list and print the yml 48 | i = 1 49 | for directory in channels: 50 | if i <= 8: 51 | samples = [] 52 | f.write(' Channel ' + str(i) + ': \r\n') 53 | #print(' Channel ' + str(i) + ': ') 54 | f.write(' Release : 0.8000\r\n') 55 | #print(' Release : 0.8000') 56 | f.write(' LinFM : 0C 0.00\r\n') 57 | #print(' LinFM : 0C 0.00') 58 | f.write(' LinAM : Off 0.00\r\n') 59 | #print(' LinAM : Off 0.00') 60 | f.write(' ZonesCV : 0B\r\n') 61 | #print(' ZonesCV : 0B') 62 | f.write(' ZonesRT : 1\r\n') 63 | #print(' ZonesRT : 1') 64 | for root, dirnames, filenames in os.walk(sample_dir + '/' + str(i)): 65 | for file in filenames: 66 | (shortname, extension) = os.path.splitext(file) 67 | if extension == '.wav': 68 | samples.append(file) 69 | 70 | samples.sort() 71 | j = 0 72 | for sample in samples: 73 | f.write(' Zone ' + str((j+1)) + ':\r\n') 74 | #print(' Zone ' + str((j+1)) + ':') 75 | f.write(' Sample : ' + sample + '\r\n') 76 | #print(' Sample : ' + sample) 77 | f.write(' MinVoltage : ' + vt_nerdseq_001[j] + '\r\n') 78 | #print(' MinVoltage : ' + vt_nerdseq_001[j]) 79 | j += 1 80 | 81 | #del sample 82 | i += 1 83 | 84 | f.close() 85 | 86 | 87 | ## Finally copy the files to the completed folder which is ready for a8 use 88 | for root, dirnames, filenames in os.walk(sample_dir): 89 | for file in filenames: 90 | (shortname, extension) = os.path.splitext(file) 91 | if extension == '.wav' and os.path.join(root,file) != sample_dir and not os.path.exists(os.path.join(dest_dir, file)): 92 | shutil.copy2(os.path.join(root,file), os.path.join(dest_dir,file)) 93 | -------------------------------------------------------------------------------- /a8_slicer.py: -------------------------------------------------------------------------------- 1 | import wave, contextlib, argparse 2 | 3 | ## Using the parser for command line arguments 4 | parser = argparse.ArgumentParser() 5 | parser.add_argument('-d', '--directory', required=True, help='This is the folder where the sample is located') 6 | parser.add_argument('-s', '--sample', required=True, help='This is the sample you want sliced') 7 | args = vars(parser.parse_args()) 8 | 9 | ## The following function formats a number so that it has leading 0s as needed for the sample start/end values 10 | def formatnumber(format_me): 11 | if len(format_me) < 8: 12 | return format_me.zfill((8 - len(format_me)) + len(format_me)) 13 | 14 | sample_dir = args['directory'] 15 | sample_name = args['sample'] 16 | file_path = sample_dir + sample_name 17 | 18 | ## The below psuedo voltage table is for the XOR NerdSeq starting at note C-4 and moving up per note 19 | vt_nerdseq_001 = ['+4.56', '+4.48', '+4.39', '+4.31', '+4.23', '+4.14', '+4.04', '-5.00'] 20 | 21 | with contextlib.closing(wave.open(file_path,'r')) as f: 22 | frames = f.getnframes() 23 | rate = f.getframerate() 24 | length = frames / float(rate) 25 | sample_length = int(round(length * frames)) 26 | division = int(round(frames/8)) 27 | 28 | count = 0 29 | part = 0 30 | 31 | while count < 8: 32 | if count == 0: 33 | print('Zone ' + str((count+1)) + ':') 34 | print(' Sample : ' + sample_name) 35 | print(' SampleEnd : ' + formatnumber(str(part + division))) 36 | print(' MinVoltage : ' + vt_nerdseq_001[count]) 37 | else: 38 | print('Zone ' + str((count+1)) + ':') 39 | print(' Sample : ' + sample_name) 40 | print(' SampleStart : ' + formatnumber(str(part))) 41 | print(' SampleEnd : ' + formatnumber(str(part + division))) 42 | print(' MinVoltage : ' + vt_nerdseq_001[count]) 43 | part = (part + division + 1) 44 | count = count + 1 45 | -------------------------------------------------------------------------------- /prst002.yml: -------------------------------------------------------------------------------- 1 | Preset 1 : 2 | Name : template001 3 | Channel 1 : 4 | Release : 2.5000 5 | ReleaseMod : 0B 1.00 6 | ZonesCV : 0A 7 | ZonesRT : 1 8 | Zone 1 : 9 | Sample : sampleA.wav 10 | MinVoltage : +4.56 11 | Zone 2 : 12 | Sample : sampleB.wav 13 | MinVoltage : +4.48 14 | Zone 3 : 15 | Sample : sampleC.wav 16 | MinVoltage : +4.39 17 | Zone 4 : 18 | Sample : sampleD.wav 19 | MinVoltage : +4.31 20 | Zone 5 : 21 | Sample : sampleE.wav 22 | MinVoltage : +4.23 23 | Zone 6 : 24 | Sample : sampleF.wav 25 | MinVoltage : +4.14 26 | Zone 7 : 27 | Sample : sampleG.wav 28 | MinVoltage : +4.04 29 | Zone 8 : 30 | Sample : sampleJ.wav 31 | MinVoltage : -5.00 32 | Channel 2 : 33 | Release : 2.5000 34 | ReleaseMod : 0B 1.00 35 | ZonesCV : 0A 36 | ZonesRT : 1 37 | Zone 1 : 38 | Sample : sampleA.wav 39 | MinVoltage : +4.56 40 | Zone 2 : 41 | Sample : sampleB.wav 42 | MinVoltage : +4.48 43 | Zone 3 : 44 | Sample : sampleC.wav 45 | MinVoltage : +4.39 46 | Zone 4 : 47 | Sample : sampleD.wav 48 | MinVoltage : +4.31 49 | Zone 5 : 50 | Sample : sampleE.wav 51 | MinVoltage : +4.23 52 | Zone 6 : 53 | Sample : sampleF.wav 54 | MinVoltage : +4.14 55 | Zone 7 : 56 | Sample : sampleG.wav 57 | MinVoltage : +4.04 58 | Zone 8 : 59 | Sample : sampleJ.wav 60 | MinVoltage : -5.00 61 | Channel 3 : 62 | Release : 2.5000 63 | ReleaseMod : 0B 1.00 64 | ZonesCV : 0A 65 | ZonesRT : 1 66 | Zone 1 : 67 | Sample : sampleA.wav 68 | MinVoltage : +4.56 69 | Zone 2 : 70 | Sample : sampleB.wav 71 | MinVoltage : +4.48 72 | Zone 3 : 73 | Sample : sampleC.wav 74 | MinVoltage : +4.39 75 | Zone 4 : 76 | Sample : sampleD.wav 77 | MinVoltage : +4.31 78 | Zone 5 : 79 | Sample : sampleE.wav 80 | MinVoltage : +4.23 81 | Zone 6 : 82 | Sample : sampleF.wav 83 | MinVoltage : +4.14 84 | Zone 7 : 85 | Sample : sampleG.wav 86 | MinVoltage : +4.04 87 | Zone 8 : 88 | Sample : sampleJ.wav 89 | MinVoltage : -5.00 90 | Channel 4 : 91 | Release : 99.0000 92 | ReleaseMod : 0C 1.00 93 | ZonesCV : 0B 94 | Zone 1 : 95 | Sample : test.wav 96 | SampleEnd : 6058 97 | MinVoltage : +4.56 98 | Zone 2 : 99 | Sample : test.wav 100 | SampleStart : 6211 101 | MinVoltage : +4.48 102 | Zone 3 : 103 | Sample : test.wav 104 | MinVoltage : +4.39 105 | Zone 4 : 106 | Sample : test.wav 107 | SampleStart : 102531 108 | MinVoltage : +4.31 109 | Zone 5 : 110 | Sample : test.wav 111 | SampleStart : 79416 112 | SampleEnd : 93964 113 | MinVoltage : +4.23 114 | Zone 6 : 115 | Sample : test.wav 116 | SampleStart : 23948 117 | SampleEnd : 39072 118 | MinVoltage : +4.14 119 | Zone 7 : 120 | Sample : test.wav 121 | SampleStart : 14595 122 | SampleEnd : 35801 123 | MinVoltage : +4.04 124 | Zone 8 : 125 | Sample : test.wav 126 | SampleEnd : 15243 127 | MinVoltage : -5.00 128 | Channel 8 : 129 | Pitch : -16.79 130 | Level : -10.0 131 | Pan : -0.30 132 | Release : 1.6000 133 | MixLevel : -90.0 134 | PMSource : 8 135 | PitchCV : 0A 0.50 136 | PMIndexMod : 0C 0.18 137 | ReleaseMod : 0B 1.00 138 | PanMod : Off 0.00 139 | LoopStartMod : 0C 0.00 140 | LoopMode : 1 141 | SpliceSmoothing : 1 142 | Zone 1 : 143 | Sample : wavetable.WAV 144 | SampleEnd : 16384 145 | LoopLength : 256.0000 146 | -------------------------------------------------------------------------------- /semitone_generator.py: -------------------------------------------------------------------------------- 1 | import math, argparse 2 | 3 | ## Using the parser for command line arguments 4 | parser = argparse.ArgumentParser() 5 | parser.add_argument('-o', '--original', required=True, help='The original bpm from the source file') 6 | parser.add_argument('-t', '--target', required=True, help='The target bpm you want to pitch up or down to') 7 | args = vars(parser.parse_args()) 8 | 9 | from math import log 10 | original_bpm = float(args['original']) 11 | adjusted_bpm = float(args['target']) 12 | semitones = (12 * log(adjusted_bpm/original_bpm)/log(2)) 13 | print(semitones) 14 | --------------------------------------------------------------------------------