Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /////////////////////////////////
- // //
- // Sample Slicer for Octatrack //
- // v1.03 //
- // by //
- // Jukka Korhonen //
- // jukka.korhonen@gmail.com //
- // @ThatBonsaipanda //
- // //
- // Feel free to modify //
- // and share //
- // //
- // Contains code from //
- // various sources //
- // //
- /////////////////////////////////
- // --------- USE AT YOUR OWN RISK --------------
- // If this app borks your entire sample library
- // I am not the one to blame for it. Always make
- // backups of your stuff before using this! :)
- // HOW TO USE
- // 1. Download aubio >> https://aubio.org/download
- // 2. Add aubioonset.exe to the path of aubioOnsetExePath (look under SETTINGS here)
- // 3. Save this file into a folder that shares the name of this file, like
- // octaslicer/octaslicer.pde
- // 4. Download Processing 3.x >> https://processing.org/download/ and install it / unzip it
- // 5. Open this file in Processing
- // 6. In the menu "Sketch", select "Add Library" and in there select "Sound"
- // 7. Press RUN in Processing. The app should now open.
- // 8. Select a single audio loop (.wav or .aif) or a folder that contains multiple loops
- // 9. The app will try to scan all audio loops for beats and transients and set slices to them
- // 10. The app will write .ot files next to the audio samples it finds
- // CAVEATS
- // No tempo detection - the algorithms I tested were heavily unreliable,
- // so you will have to manually set the tempo for each loop in the Octatrack.
- // Some other loop settings might not be correct, I tried my best to set everything
- // in their respected places, but there is the off chance that things are wrong.
- // Checksum calculation is wrong. This will result in an error when loading the sample
- // into the Octatrack, however, the data and slices are ok. Sort of.
- // -------- SETTINGS ----------------------------
- String aubioOnsetExePath = "J:/utility/audio/octatrack/beat_detection/aubio-0.4.6-win64/bin/aubioonset.exe"; // Set this manually
- float detectionSensitivity = 0.3;
- String detectionAlgorithm = "default"; // default|energy|hfc|complex|phase|specdiff|kl|mkl|specflux, default=hfc
- int defaultTempo = 128;
- // ----------------------------------------------
- import java.io.BufferedReader;
- import java.io.InputStreamReader;
- import java.io.IOException;
- import java.nio.ByteBuffer;
- import java.nio.ByteOrder;
- import java.util.Arrays;
- import java.util.regex.*;
- import processing.sound.*;
- ArrayList<Button> buttons = new ArrayList<Button>();
- enum ButtonType { file, folder, processorLocation };
- String lastOpenedPath = "";
- void setup () {
- size (450, 140);
- }
- void draw () {
- GUI();
- }
- void SaveLastPath (File target) {
- String path = target.getParent();
- if (target.isDirectory()) {
- path = target.getAbsolutePath();
- }
- println("Last opened >> " + path);
- lastOpenedPath = path;
- }
- void mouseReleased () {
- if (mouseButton == LEFT) {
- for (int i = 0; i < buttons.size(); i++) {
- boolean hit = buttons.get(i).CheckHit(mouseX, mouseY);
- if (hit)
- {
- if (buttons.get(i).type == ButtonType.file) { OpenFile(); break; }
- if (buttons.get(i).type == ButtonType.folder) { OpenFolder(); break; }
- if (buttons.get(i).type == ButtonType.processorLocation) { OpenProcessorFolder(); break; }
- }
- }
- }
- }
- // Entry point
- void OpenFile () {
- selectInput("Select an audio loop sample to process:", "FileSelected");
- }
- // Entry point
- void OpenFolder () {
- File target = new File(lastOpenedPath);
- if (target.exists()) {
- selectFolder("Select a folder with loop samples:", "FolderSelected", target);
- }
- else {
- selectFolder("Select a folder with loop samples:", "FolderSelected");
- }
- }
- // Debug entry point
- void OpenProcessorFolder () {
- selectInput("Select the location of aubioonset.exe:", "ProcessorSelected");
- }
- void ProcessFolder (File folder) {
- File[] filesInFolder = folder.listFiles();
- if (filesInFolder != null)
- {
- for (File file : filesInFolder)
- {
- if (IsAudioFile(file))
- {
- ProcessFile(file);
- }
- }
- }
- else
- {
- }
- println("Processing folder finished.");
- }
- void ProcessFile (File file) {
- String filepath = file.getAbsolutePath();
- println("Processing file >> " + file.getName());
- String outFilePath = filepath.substring(0, filepath.length() - 4);
- ArrayList<Long> outputBuffer = new ArrayList<Long>();
- Process p = null;
- InputStream error = null;
- int slicesAmount = 0;
- int tempo = GetTempoFromFileName(file.getName());
- float duration = 0;
- // Processing's 'Sound' -library needs to be installed for this
- SoundFile s = new SoundFile(this, file.getAbsolutePath());
- duration = s.duration();
- long samples = (long)(duration * s.sampleRate());
- println("Duration >> " + duration + "s");
- println("Samplerate >> " + s.sampleRate() + "Hz");
- println("Total samples >> " + samples);
- println("Tempo >> " + tempo);
- try
- {
- String[] params = { aubioOnsetExePath,
- "-i", filepath,
- "-t", "" + detectionSensitivity,
- "-O", detectionAlgorithm,
- "-T", "samples" };
- p = Runtime.getRuntime().exec(params);
- final BufferedReader
- out = new BufferedReader(new InputStreamReader(p.getInputStream())),
- err = new BufferedReader(new InputStreamReader(p.getErrorStream()));
- outputBuffer.clear();
- String read;
- while ((read = out.readLine()) != null)
- {
- outputBuffer.add(Long.parseLong(read));
- }
- error = p.getErrorStream();
- BufferedReader errorReader = new BufferedReader(new InputStreamReader(error));
- while ((read = errorReader.readLine()) != null)
- {
- println(read);
- }
- Thread.sleep(4000);
- p.destroy();
- }
- catch (Exception e)
- {
- e.printStackTrace();
- }
- slicesAmount = outputBuffer.size();
- // ------- CREATE AND WRITE THE .ot FILE ---------------
- OTData data = new OTData();
- data.tempo = tempo;
- // TODO
- // Calculate the bars
- // Almost there, how do we know what beat it is?
- // We're assuming it's 4/4 but that's not always the case
- int bars = (int)((duration * 1000) / (60000 / tempo)) / 4;
- data.trimlen = bars;
- data.looplen = bars;
- data.trimEnd = samples;
- data.sliceCount = slicesAmount;
- println("Bars >> " + bars);
- println("Slices >> " + slicesAmount);
- tempSlices = new ArrayList<Slice>();
- // Octatrack supports only 64 slices per audio clip
- if (slicesAmount > 64) { slicesAmount = 64; }
- if (slicesAmount > 0)
- {
- for (int i = 0; i < slicesAmount - 1; i++)
- {
- AddSlice(outputBuffer.get(i), outputBuffer.get(i + 1) - 1);
- }
- AddSlice(outputBuffer.get(slicesAmount - 1), samples);
- }
- saveBytes(file.getParent() + "/" + file.getName().replaceFirst("[.][^.]+$", "") + ".ot", data.GetBytes());
- // ---------------------------------------------------------------------
- println("File processed.");
- }
- void ProcessorSelected(File target) {
- if (target == null) {
- println("Window was closed or the user hit cancel.");
- }
- else {
- println("Processor selected >> " + target.getAbsolutePath());
- aubioOnsetExePath = target.getAbsolutePath();
- }
- }
- void FileSelected(File file) {
- if (file == null) {
- println("Window was closed or the user hit cancel.");
- }
- else {
- println("Processing >> " + file.getAbsolutePath());
- // SaveLastPath(file);
- ProcessFile(file);
- }
- }
- void FolderSelected(File target) {
- if (target == null) {
- println("Window was closed or the user hit cancel.");
- }
- else {
- println("Processing >> " + target.getAbsolutePath());
- SaveLastPath(target);
- ProcessFolder(target);
- }
- }
- boolean IsAudioFile (File file) {
- String ext = GetFileExtension(file);
- println("Comparing extension to >> " + file.getName());
- if (ext.equals(".wav") || ext.equals(".WAV") ||
- ext.equals(".aif") || ext.equals(".AIF") ||
- ext.equals(".aiff") || ext.equals(".AIFF"))
- {
- return true;
- }
- return false;
- }
- String GetFileExtension(File file) {
- String extension = "";
- try {
- if (file != null && file.exists()) {
- String name = file.getName();
- extension = name.substring(name.lastIndexOf("."));
- }
- }
- catch (Exception e) {
- extension = "";
- }
- return extension;
- }
- int GetTempoFromFileName (String name) {
- Matcher matcher = Pattern.compile("\\d+").matcher(name);
- // matcher.find();
- int result = 0;
- // TODO
- // This is a hack, best would be to split
- // the filename and look for a number that's isolated.
- // Filenames like SO5_128_drum_top_01.wav would fail
- // without the limit of 40 BPM
- int breakout = 0;
- while (result < 40) {
- if (matcher.find()) {
- result = Integer.valueOf(matcher.group());
- }
- breakout++;
- if (breakout > 20) { result = defaultTempo; break; }
- }
- return result;
- }
- // TODO
- // This calculation is wrong
- int Checksum (byte[] data) {
- int checkSum = 0;
- for(int i = 16; i < data.length - 1; i++) {
- checkSum += data[i] & 0xff;
- }
- return checkSum;
- }
- boolean guiCreated = false;
- void GUI () {
- if (!guiCreated)
- {
- AddButton(20, 20, 200, 32, "Process a Loop", ButtonType.file);
- AddButton(230, 20, 200, 32, "Process a Folder", ButtonType.folder);
- // AddButton(20, 60, 200, 32, "Locate aubioonset.exe", ButtonType.processorLocation);
- }
- for (int i = 0; i < buttons.size(); i++)
- {
- Button button = buttons.get(i);
- fill(255);
- rect(button.x, button.y, button.w, button.h);
- fill(0, 100, 150);
- textAlign(CENTER, CENTER);
- textSize(16);
- text(button.label, button.x, button.y, button.w, button.h);
- }
- }
- void AddButton (int x, int y, int w, int h, String label, ButtonType type) {
- Button button = new Button();
- button.x = x;
- button.y = y;
- button.w = w;
- button.h = h;
- button.label = label;
- button.type = type;
- buttons.add(button);
- }
- class Button {
- ButtonType type = ButtonType.file;
- int x = 0;
- int y = 0;
- int w = 200;
- int h = 50;
- String label = "Button";
- boolean CheckHit (int hitX, int hitY) {
- if (hitX >= x && hitX <= x + w &&
- hitY >= y && hitY <= y + h) {
- return true;
- }
- else
- {
- return false;
- }
- }
- }
- // Octatrack specific stuff
- ArrayList<Slice> tempSlices;
- void AddSlice (long start, long end) {
- Slice slice = new Slice();
- slice.loopPoint = 0xffffffffL;
- slice.startPoint = start;
- slice.endPoint = end;
- tempSlices.add(slice);
- }
- class OTData {
- // 8-bit mask is 0xff
- // 16-bit mask is 0xffff
- // 32-bit mask is 0xffffffffL;
- byte header[] = { 0x46, 0x4F, 0x52, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x44, 0x50, 0x53, 0x31, 0x53, 0x4D, 0x50, 0x41 };
- // 8-bit, 0x00
- byte unknown[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00 };
- // 8-bit, 0x10 (All blank except 0x15 = 2)
- long tempo = defaultTempo; // 32-bit, 0x17 (BPM * 24)
- long trimlen = 0; // 32-bit, 0x1B (value * 100)
- long looplen = 0; // 32-bit, 0x1F (value * 100)
- long stretch = 0; // 32-bit, 0x23 (off = 0, normal = 2, beat = 3)
- long loop = 0; // 32-bit, 0x27 (off = 0, normal = 1, pingpong = 2)
- int gain = 48; // 16-bit, 0x2B (0x30 = 0, 0x60 = 24 (max), 0x00 = -24 (min))
- byte quantize = 0x01; // 8-bit, 0x2D (0x00 = pattern length, 0xff = direct, 1-10 = 1,2,3,4,6,8,12,16,24,32,48,64,96,128,192,256) 0x01 = 1/16 trig
- long trimStart = 0; // 32-bit, 0x2E
- long trimEnd = 0; // 32-bit, 0x32
- long loopPoint = 0; // 32-bit, 0x36
- // Slice slices; // 32-bit, 0x3A - 0x339
- long sliceCount = 0; // 32-bit, 0x33A
- int checkSum = 0; // 16-bit, 0x33E
- byte[] GetBytes () {
- byte[] data = new byte[832];
- arrayCopy(header, 0, data, 0, 16);
- arrayCopy(unknown, 0, data, 0x10, 7);
- byte[] b = BytesFromUInt32(defaultTempo * 24); arrayCopy(b, 0, data, 0x17, 4);
- b = BytesFromUInt32(trimlen * 100); arrayCopy(b, 0, data, 0x1B, 4);
- b = BytesFromUInt32(looplen * 100); arrayCopy(b, 0, data, 0x1F, 4);
- b = BytesFromUInt32(stretch); arrayCopy(b, 0, data, 0x23, 4);
- b = BytesFromUInt32(loop); arrayCopy(b, 0, data, 0x27, 4);
- b = BytesFromUInt16(gain); arrayCopy(b, 0, data, 0x2B, 2);
- data[0x2D] = quantize;
- b = BytesFromUInt32(trimStart); arrayCopy(b, 0, data, 0x2E, 4);
- b = BytesFromUInt32(trimEnd); arrayCopy(b, 0, data, 0x32, 4);
- b = BytesFromUInt32(loopPoint); arrayCopy(b, 0, data, 0x36, 4);
- int index = 0x3A;
- for (int i = 0; i < tempSlices.size(); i++) {
- b = tempSlices.get(i).GetBytes();
- arrayCopy(b, 0, data, index, 12); index += 12;
- }
- b = BytesFromUInt32(sliceCount); arrayCopy(b, 0, data, 0x33A, 4);
- b = BytesFromUInt16(Checksum(data)); arrayCopy(b, 0, data, 0x33E, 2);
- return data;
- }
- }
- class Slice {
- long startPoint = 0;
- long endPoint = 0;
- long loopPoint = 0xffffffffL;
- byte[] GetBytes () {
- int index = 0;
- byte[] data = new byte[24];
- byte[] b = BytesFromUInt32(startPoint);
- arrayCopy(b, 0, data, index, 4);
- index += 4;
- b = BytesFromUInt32(endPoint);
- arrayCopy(b, 0, data, index, 4);
- index += 4;
- b = BytesFromUInt32(loopPoint);
- arrayCopy(b, 0, data, index, 4);
- index += 4;
- return data;
- }
- }
- byte[] BytesFromUInt32(long value) {
- byte[] bytes = new byte[8];
- ByteBuffer.wrap(bytes).putLong(value);
- return Arrays.copyOfRange(bytes, 4, 8);
- }
- byte[] BytesFromUInt16(int value) {
- byte[] bytes = new byte[4];
- ByteBuffer.wrap(bytes).putInt(value);
- return Arrays.copyOfRange(bytes, 2, 4);
- }
- int UInt16FromBytes (byte [] bytes) {
- return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement