Loadus

OctatrackSampleSlicer.pde

Apr 19th, 2019
319
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 13.68 KB | None | 0 0
  1. /////////////////////////////////
  2. //                             //
  3. // Sample Slicer for Octatrack //
  4. //          v1.03              //
  5. //           by                //
  6. //     Jukka Korhonen          //
  7. //     @ThatBonsaipanda        //
  8. //                             //
  9. //    Feel free to modify      //
  10. //         and share           //
  11. //                             //
  12. //    Contains code from       //
  13. //     various sources         //
  14. //                             //
  15. /////////////////////////////////
  16.  
  17. // --------- USE AT YOUR OWN RISK --------------
  18. // If this app borks your entire sample library
  19. // I am not the one to blame for it. Always make
  20. // backups of your stuff before using this! :)
  21.  
  22. // HOW TO USE
  23. // 1. Download aubio >> https://aubio.org/download
  24. // 2. Add aubioonset.exe to the path of aubioOnsetExePath (look under SETTINGS here)
  25. // 3. Save this file into a folder that shares the name of this file, like
  26. //    octaslicer/octaslicer.pde
  27. // 4. Download Processing 3.x >> https://processing.org/download/ and install it / unzip it
  28. // 5. Open this file in Processing
  29. // 6. In the menu "Sketch", select "Add Library" and in there select "Sound"
  30. // 7. Press RUN in Processing. The app should now open.
  31. // 8. Select a single audio loop (.wav or .aif) or a folder that contains multiple loops
  32. // 9. The app will try to scan all audio loops for beats and transients and set slices to them
  33. // 10. The app will write .ot files next to the audio samples it finds
  34.  
  35. // CAVEATS
  36. // No tempo detection - the algorithms I tested were heavily unreliable,
  37. // so you will have to manually set the tempo for each loop in the Octatrack.
  38. // Some other loop settings might not be correct, I tried my best to set everything
  39. // in their respected places, but there is the off chance that things are wrong.
  40. // Checksum calculation is wrong. This will result in an error when loading the sample
  41. // into the Octatrack, however, the data and slices are ok. Sort of.
  42.  
  43.  
  44. // -------- SETTINGS ----------------------------
  45.  
  46.     String aubioOnsetExePath = "J:/utility/audio/octatrack/beat_detection/aubio-0.4.6-win64/bin/aubioonset.exe";    // Set this manually
  47.     float detectionSensitivity = 0.3;
  48.     String detectionAlgorithm = "default";  //  default|energy|hfc|complex|phase|specdiff|kl|mkl|specflux, default=hfc
  49.     int defaultTempo = 128;
  50.  
  51. // ----------------------------------------------
  52.  
  53. import java.io.BufferedReader;
  54. import java.io.InputStreamReader;
  55. import java.io.IOException;
  56. import java.nio.ByteBuffer;
  57. import java.nio.ByteOrder;
  58. import java.util.Arrays;
  59. import java.util.regex.*;
  60. import processing.sound.*;
  61.  
  62. ArrayList<Button> buttons = new ArrayList<Button>();
  63. enum ButtonType { file, folder, processorLocation };
  64. String lastOpenedPath = "";
  65.  
  66.  
  67.  
  68. void setup () {
  69.     size (450, 140);
  70. }
  71.  
  72. void draw () {
  73.     GUI();
  74. }
  75.  
  76.  
  77. void SaveLastPath (File target) {
  78.     String path = target.getParent();
  79.     if (target.isDirectory()) {
  80.         path = target.getAbsolutePath();
  81.     }
  82.     println("Last opened >> " + path);
  83.     lastOpenedPath = path;
  84. }
  85.  
  86.  
  87.  
  88.  
  89.  
  90. void mouseReleased () {
  91.     if (mouseButton == LEFT) {
  92.         for (int i = 0; i < buttons.size(); i++) {
  93.             boolean hit = buttons.get(i).CheckHit(mouseX, mouseY);
  94.             if (hit)
  95.             {
  96.                 if (buttons.get(i).type == ButtonType.file) { OpenFile(); break; }
  97.                 if (buttons.get(i).type == ButtonType.folder) { OpenFolder(); break; }
  98.                 if (buttons.get(i).type == ButtonType.processorLocation) { OpenProcessorFolder(); break; }         
  99.             }
  100.         }
  101.     }  
  102. }
  103.  
  104. // Entry point
  105. void OpenFile () {
  106.     selectInput("Select an audio loop sample to process:", "FileSelected");
  107. }
  108.  
  109. // Entry point
  110. void OpenFolder () {
  111.     File target = new File(lastOpenedPath);
  112.     if (target.exists()) {
  113.         selectFolder("Select a folder with loop samples:", "FolderSelected", target);
  114.     }
  115.     else {  
  116.         selectFolder("Select a folder with loop samples:", "FolderSelected");
  117.     }
  118. }
  119.  
  120. // Debug entry point
  121. void OpenProcessorFolder () {
  122.     selectInput("Select the location of aubioonset.exe:", "ProcessorSelected");
  123. }
  124.  
  125. void ProcessFolder (File folder) {
  126.     File[] filesInFolder = folder.listFiles();
  127.     if (filesInFolder != null)
  128.     {
  129.         for (File file : filesInFolder)
  130.         {
  131.             if (IsAudioFile(file))
  132.             {
  133.                 ProcessFile(file);
  134.             }
  135.         }
  136.     }
  137.     else
  138.     {
  139.  
  140.     }
  141.  
  142.     println("Processing folder finished.");
  143. }
  144.  
  145. void ProcessFile (File file) {
  146.     String filepath = file.getAbsolutePath();
  147.     println("Processing file >> " + file.getName());
  148.     String outFilePath = filepath.substring(0, filepath.length() - 4);
  149.     ArrayList<Long> outputBuffer = new ArrayList<Long>();
  150.     Process p = null;
  151.     InputStream error = null;
  152.     int slicesAmount = 0;
  153.     int tempo = GetTempoFromFileName(file.getName());
  154.     float duration = 0;
  155.  
  156.         // Processing's 'Sound' -library needs to be installed for this
  157.         SoundFile s = new SoundFile(this, file.getAbsolutePath());
  158.         duration = s.duration();
  159.         long samples = (long)(duration * s.sampleRate());
  160.         println("Duration >> " + duration + "s");
  161.         println("Samplerate >> " + s.sampleRate() + "Hz");
  162.         println("Total samples >> " + samples);
  163.         println("Tempo >> " + tempo);
  164.  
  165.     try
  166.     {
  167.         String[] params = { aubioOnsetExePath,
  168.                             "-i", filepath,
  169.                             "-t", "" + detectionSensitivity,
  170.                             "-O", detectionAlgorithm,
  171.                             "-T", "samples" };
  172.         p = Runtime.getRuntime().exec(params);
  173.         final BufferedReader
  174.             out = new BufferedReader(new InputStreamReader(p.getInputStream())),
  175.             err = new BufferedReader(new InputStreamReader(p.getErrorStream()));
  176.        
  177.         outputBuffer.clear();
  178.        
  179.         String read;
  180.         while ((read = out.readLine()) != null)
  181.         {
  182.             outputBuffer.add(Long.parseLong(read));
  183.         }
  184.  
  185.         error = p.getErrorStream();    
  186.         BufferedReader errorReader = new BufferedReader(new InputStreamReader(error));
  187.         while ((read = errorReader.readLine()) != null)
  188.         {
  189.             println(read);
  190.         }
  191.  
  192.         Thread.sleep(4000);
  193.         p.destroy();
  194.     }
  195.  
  196.     catch (Exception e)
  197.     {
  198.         e.printStackTrace();
  199.     }
  200.  
  201.     slicesAmount = outputBuffer.size();
  202.  
  203.     // ------- CREATE AND WRITE THE .ot FILE ---------------
  204.        
  205.         OTData data = new OTData();
  206.             data.tempo = tempo;
  207.             // TODO
  208.             // Calculate the bars
  209.             // Almost there, how do we know what beat it is?
  210.             // We're assuming it's 4/4 but that's not always the case
  211.             int bars = (int)((duration * 1000) / (60000 / tempo)) / 4;
  212.             data.trimlen = bars;
  213.             data.looplen = bars;
  214.             data.trimEnd = samples;
  215.             data.sliceCount = slicesAmount;
  216.             println("Bars >> " + bars);
  217.             println("Slices >> " + slicesAmount);
  218.  
  219.         tempSlices = new ArrayList<Slice>();
  220.  
  221.         // Octatrack supports only 64 slices per audio clip
  222.         if (slicesAmount > 64) { slicesAmount = 64; }
  223.  
  224.         if (slicesAmount > 0)
  225.         {
  226.             for (int i = 0; i < slicesAmount - 1; i++)
  227.             {
  228.                 AddSlice(outputBuffer.get(i), outputBuffer.get(i + 1) - 1);
  229.             }
  230.  
  231.             AddSlice(outputBuffer.get(slicesAmount - 1), samples);
  232.         }  
  233.  
  234.         saveBytes(file.getParent() + "/" + file.getName().replaceFirst("[.][^.]+$", "") + ".ot", data.GetBytes());
  235.  
  236.     // ---------------------------------------------------------------------
  237.  
  238.     println("File processed.");
  239. }
  240.  
  241. void ProcessorSelected(File target) {
  242.     if (target == null) {
  243.         println("Window was closed or the user hit cancel.");
  244.     }
  245.     else {
  246.         println("Processor selected >> " + target.getAbsolutePath());
  247.         aubioOnsetExePath = target.getAbsolutePath();
  248.     }
  249. }
  250.  
  251. void FileSelected(File file) {
  252.     if (file == null) {
  253.         println("Window was closed or the user hit cancel.");
  254.     }
  255.     else {
  256.         println("Processing >> " + file.getAbsolutePath());
  257.         // SaveLastPath(file);
  258.         ProcessFile(file);
  259.     }
  260. }
  261.  
  262. void FolderSelected(File target) {
  263.     if (target == null) {
  264.         println("Window was closed or the user hit cancel.");
  265.     }
  266.     else {
  267.         println("Processing >> " + target.getAbsolutePath());
  268.         SaveLastPath(target);
  269.         ProcessFolder(target);
  270.     }
  271. }
  272.  
  273. boolean IsAudioFile (File file) {
  274.     String ext = GetFileExtension(file);
  275.     println("Comparing extension to >> " + file.getName());
  276.     if (ext.equals(".wav") || ext.equals(".WAV") ||
  277.         ext.equals(".aif") || ext.equals(".AIF") ||
  278.         ext.equals(".aiff") || ext.equals(".AIFF"))
  279.     {
  280.         return true;
  281.     }
  282.  
  283.     return false;
  284. }
  285.  
  286. String GetFileExtension(File file) {
  287.     String extension = "";
  288.     try {
  289.         if (file != null && file.exists()) {
  290.             String name = file.getName();
  291.             extension = name.substring(name.lastIndexOf("."));
  292.         }
  293.     }
  294.     catch (Exception e) {
  295.         extension = "";
  296.     }
  297.  
  298.     return extension;
  299. }
  300.  
  301. int GetTempoFromFileName (String name) {
  302.     Matcher matcher = Pattern.compile("\\d+").matcher(name);
  303.             // matcher.find();
  304.     int result = 0;
  305.     // TODO
  306.     // This is a hack, best would be to split
  307.     // the filename and look for a number that's isolated.
  308.     // Filenames like SO5_128_drum_top_01.wav would fail
  309.     // without the limit of 40 BPM
  310.     int breakout = 0;
  311.     while (result < 40) {
  312.         if (matcher.find()) {
  313.             result = Integer.valueOf(matcher.group());
  314.         }
  315.         breakout++;
  316.         if (breakout > 20) { result = defaultTempo; break; }
  317.     }
  318.     return result;
  319. }
  320.  
  321.  
  322. // TODO
  323. // This calculation is wrong
  324. int Checksum (byte[] data) {
  325.     int checkSum = 0;
  326.     for(int i = 16; i < data.length - 1; i++) {
  327.         checkSum += data[i] & 0xff;
  328.     }
  329.     return checkSum;
  330. }
  331.  
  332.  
  333. boolean guiCreated = false;
  334.  
  335. void GUI () {
  336.     if (!guiCreated)
  337.     {
  338.         AddButton(20, 20, 200, 32, "Process a Loop", ButtonType.file);
  339.         AddButton(230, 20, 200, 32, "Process a Folder", ButtonType.folder);
  340.         // AddButton(20, 60, 200, 32, "Locate aubioonset.exe", ButtonType.processorLocation);
  341.     }
  342.  
  343.     for (int i = 0; i < buttons.size(); i++)
  344.     {
  345.         Button button = buttons.get(i);
  346.         fill(255);
  347.         rect(button.x, button.y, button.w, button.h);
  348.         fill(0, 100, 150);
  349.         textAlign(CENTER, CENTER);
  350.         textSize(16);
  351.         text(button.label, button.x, button.y, button.w, button.h);
  352.     }
  353. }
  354.  
  355. void AddButton (int x, int y, int w, int h, String label, ButtonType type) {
  356.     Button button = new Button();
  357.         button.x = x;
  358.         button.y = y;
  359.         button.w = w;
  360.         button.h = h;
  361.         button.label = label;
  362.         button.type = type;
  363.     buttons.add(button);
  364. }
  365.  
  366. class Button {
  367.  
  368.     ButtonType type = ButtonType.file;
  369.  
  370.     int x = 0;
  371.     int y = 0;
  372.     int w = 200;
  373.     int h = 50;
  374.     String label = "Button";
  375.  
  376.     boolean CheckHit (int hitX, int hitY) {
  377.         if (hitX >= x && hitX <= x + w &&
  378.             hitY >= y && hitY <= y + h) {
  379.             return true;
  380.         }  
  381.         else
  382.         {
  383.             return false;
  384.         }      
  385.     }
  386. }
  387.  
  388.  
  389.  
  390.  
  391.  
  392.  
  393.  
  394.  
  395.  
  396. // Octatrack specific stuff
  397.  
  398. ArrayList<Slice> tempSlices;
  399.  
  400.  
  401. void AddSlice (long start, long end) {
  402.     Slice slice = new Slice();
  403.     slice.loopPoint = 0xffffffffL;
  404.     slice.startPoint = start;
  405.     slice.endPoint = end;
  406.     tempSlices.add(slice);
  407. }
  408.  
  409. class OTData {
  410.                                     // 8-bit mask is 0xff
  411.                                     // 16-bit mask is 0xffff
  412.                                     // 32-bit mask is 0xffffffffL;
  413.     byte header[] = { 0x46, 0x4F, 0x52, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x44, 0x50, 0x53, 0x31, 0x53, 0x4D, 0x50, 0x41 };
  414.                                     // 8-bit,   0x00
  415.     byte unknown[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00 }; 
  416.                                     // 8-bit,   0x10 (All blank except 0x15 = 2)
  417.     long tempo = defaultTempo;      // 32-bit,  0x17 (BPM * 24)
  418.     long trimlen = 0;               // 32-bit,  0x1B (value * 100)
  419.     long looplen = 0;               // 32-bit,  0x1F (value * 100)
  420.     long stretch = 0;               // 32-bit,  0x23 (off = 0, normal = 2, beat = 3)
  421.     long loop = 0;                  // 32-bit,  0x27 (off = 0, normal = 1, pingpong = 2)
  422.     int gain = 48;                  // 16-bit,  0x2B (0x30 = 0, 0x60 = 24 (max), 0x00 = -24 (min))
  423.     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
  424.     long trimStart = 0;             // 32-bit,  0x2E
  425.     long trimEnd = 0;               // 32-bit,  0x32
  426.     long loopPoint = 0;             // 32-bit,  0x36
  427.     // Slice slices;                // 32-bit,  0x3A - 0x339
  428.     long sliceCount = 0;            // 32-bit,  0x33A
  429.     int checkSum = 0;               // 16-bit,  0x33E
  430.  
  431.     byte[] GetBytes () {
  432.         byte[] data = new byte[832];
  433.  
  434.                                                         arrayCopy(header, 0, data, 0, 16);
  435.                                                         arrayCopy(unknown, 0, data, 0x10, 7);      
  436.         byte[] b = BytesFromUInt32(defaultTempo * 24);  arrayCopy(b, 0, data, 0x17, 4);    
  437.         b = BytesFromUInt32(trimlen * 100);             arrayCopy(b, 0, data, 0x1B, 4);    
  438.         b = BytesFromUInt32(looplen * 100);             arrayCopy(b, 0, data, 0x1F, 4);    
  439.         b = BytesFromUInt32(stretch);                   arrayCopy(b, 0, data, 0x23, 4);    
  440.         b = BytesFromUInt32(loop);                      arrayCopy(b, 0, data, 0x27, 4);    
  441.         b = BytesFromUInt16(gain);                      arrayCopy(b, 0, data, 0x2B, 2);
  442.         data[0x2D] = quantize;
  443.         b = BytesFromUInt32(trimStart);                 arrayCopy(b, 0, data, 0x2E, 4);    
  444.         b = BytesFromUInt32(trimEnd);                   arrayCopy(b, 0, data, 0x32, 4);
  445.         b = BytesFromUInt32(loopPoint);                 arrayCopy(b, 0, data, 0x36, 4);
  446.        
  447.         int index = 0x3A;
  448.         for (int i = 0; i < tempSlices.size(); i++) {
  449.             b = tempSlices.get(i).GetBytes();
  450.             arrayCopy(b, 0, data, index, 12); index += 12;
  451.         }  
  452.        
  453.         b = BytesFromUInt32(sliceCount); arrayCopy(b, 0, data, 0x33A, 4);
  454.         b = BytesFromUInt16(Checksum(data)); arrayCopy(b, 0, data, 0x33E, 2);
  455.        
  456.         return data;
  457.     }
  458. }
  459.  
  460. class Slice {
  461.     long startPoint = 0;
  462.     long endPoint = 0;
  463.     long loopPoint = 0xffffffffL;
  464.  
  465.     byte[] GetBytes () {
  466.         int index = 0;
  467.         byte[] data = new byte[24];
  468.         byte[] b = BytesFromUInt32(startPoint);
  469.         arrayCopy(b, 0, data, index, 4);
  470.             index += 4;
  471.         b = BytesFromUInt32(endPoint);
  472.         arrayCopy(b, 0, data, index, 4);
  473.             index += 4;
  474.         b = BytesFromUInt32(loopPoint);
  475.         arrayCopy(b, 0, data, index, 4);
  476.             index += 4;
  477.  
  478.         return data;
  479.     }
  480. }
  481.  
  482.  
  483. byte[] BytesFromUInt32(long value) {
  484.     byte[] bytes = new byte[8];
  485.     ByteBuffer.wrap(bytes).putLong(value);
  486.     return Arrays.copyOfRange(bytes, 4, 8);
  487. }
  488.  
  489. byte[] BytesFromUInt16(int value) {
  490.     byte[] bytes = new byte[4];
  491.     ByteBuffer.wrap(bytes).putInt(value);
  492.     return Arrays.copyOfRange(bytes, 2, 4);
  493. }
  494.  
  495. int UInt16FromBytes (byte [] bytes) {
  496.     return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
  497. }
Advertisement
Add Comment
Please, Sign In to add comment