Advertisement
Loadus

OctatrackSampleSlicer.pde

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