The Compleat ROBO, Part 2: File Management and Basic Recording

    Welcome to the second part of our tutorial on creating an automatic sampling system! This time out, we're going to do a little more preparation, and get into the recording process.
    All the tutorials in this series: Part 1, Part 2, Part 3.


    Now that we know we can create the range of notes that we need and are able to verify that they fire properly, we need to actually do some audio recording. In order to do this, we will need to do a couple of things:
    1. We need to set up a naming structure for our system.
    2. We need to decide where to store the files.
    3. Finally, we need to do the actual audio recording.
    This is could be kind of tricky, but we are going to take advantage (again) of some of the flexibility of Javascript to help bring this one home.

    Creating a Naming Convention

    Let’s start off by deciding on the file names and locations that we are going to use. For the storage location, we are just going to get a folder from the operating system. We’ll do that by adding the following to our UI patch:
    This will give me a button to press to set the location folder (that’s what the ‘fold’ argument is for the opendialog object), and a textedit field, with clicks disabled, as a place to show the result. Putting it into my UI and moving things around a bit gives me this interface:
    Next we need a scheme to name the files we record. I initially thought that I’d want to enter an instrument name - something like “Big Piano” - and have note and velocity information appended to that. But you know what? I also know how I work, and the fact is that I’m kind of lazy. It's quite likely that at some future point I’ll forget to change the name, then I’ll have a bunch of snare drums labeled “Big Piano”. Not optimal!
    So instead, I’m going to use a timestamp - but I don’t want to timestamp each individual file as it is created for two reasons:
    1. I wouldn’t know which samples are grouped together.
    2. I couldn’t use simple tools to rename the files.
    Instead, I want to name everything based on when the sample run starts, then append the note and velocity. I decided on the following naming convention:
    This means that a filename might look something like this:
    Now I can quickly see when this run started, and which Note (#36) and Velocity (60) were used to generate this particular file. We’ll need to make some changes to the Javascript we developed in the first installment of this tutorial in order to support this concept.
    I’ll start by making some changes to the data storage. I’m going to add another outlet (for the filename) by changing the outlets assignment. I’m also going to add a variable to hold the folder for output - named ‘locfolder’ so it doesn’t interfere with the function named ‘folder’. Finally, I'll add a string for the date-stamped name (with some contents as a reminder for the format).
    Next, I need some new functions:
    1. I need a function to update the folder (which I get from the UI).
    2. I need a function that generates date-time-based filename when a sample run starts.
    3. Finally, I need a little utility function do some zero padding - to turn a single number into a two-digit or three-digit numbers (i.e., “9” becomes either "09" or “009”).
    Finally, I’ve abstracted the output into its own function so that I can always output the filename whenever I output a note. I then change the start() function to also set up the file naming convention, and both the start() and next() routines to use our new doNote() function. We also have to change the ‘bang’ outlet on the stop() routine:
    Save the Javascript, and jump to the Processing.maxpat patch - we now have three outlets for the js object. Let’s shuffle things around, then run our patch to make sure it still works!
    And we are off!

    Performing the Recording

    Now that we are generating a file name, it’s time to actually record a file. We are going to do a rather simple version of recording for this week, and make it more robust next week. But let’s take a swing at what we need:
    1. When we get a file name, we start recording to disk.
    2. After a set period of time, we stop recording.
    3. Move the ‘next note’ logic from the note player to pay attention to the recorder.
    Let’s start with a few modifications. First, we’ll change the Javascript file’s second outlet destination to a “to_Recording” send. Then we’ll remove the ‘next note’ logic from the note_Player patcher:
    Now, we’ll create a new patcher - audio_Recording.maxpat - and put in some basic audio recording function. First, we paste in the ‘next note’ logic from our earlier patch. Then, we get audio input from an adc~ object, push it into an sfrecord~ function, and get a little feedback that it is working:
    Next, let’s check off those items on the list. When we receive a file name, let’s tell sfrecord~ to open it, then tell it to record for 2 seconds (2000 milliseconds).
    Then, let’s change the duration of the delay in our ‘next note’ logic to be a little longer than the recording time. And while we are in there, let's make sure we can hear our process by setting a dac~ to receive the input and route it outward:
    This is mighty simple programming; let’s look at the result:
    OK, so we captured the audio just fine, but there is a lot of room at the end, and a little gap at the front. Not perfect - so something we will have to work on. But in the meantime, let's look at cleaning up those sample file names. I use a utility program called NameChanger that lets me select a set of files and do a group name change. In this case, I'm changing the timestamp to "CoolSynth" to properly frame the result:
    We've got a little work to do on that sample recording - but at least we are recording! Next week we’ll dive into the recording function a little deeper, but for now, we have the start of a pretty credible ROBO-sampler. If you want to expand on this, you can play with changing the note time, recording time or even add additional functions for controller changes. Just have fun and explore!

    ROBO 2 Test - Basic Recording

    And here's the updated Javascript file...
    // ------------------------------------------------ // midiproc.js - the note processor for ROBO // v 2.0 - includes name processing // ------------------------------------------------
    inlets = 1; outlets = 3; // 1:notes, 2:filename, 3:bang on complete
    autowatch = 1;
    var n_start = 36; var n_end = 96; var n_interval = 3;
    var v_start = 7; var v_end = 127; var v_interval = 10;
    var locfolder = ""; var datename = "00000000-000000-N000V000";
    var is_running = false; var curr_note, curr_vel;
    function note_start(v) { n_start = v; }
    function note_end(v) { n_end = v; }
    function note_interval(v) { n_interval = v; }
    function vel_start(v) { v_start = v; }
    function vel_end(v) { v_end = v; }
    function vel_interval(v) { v_interval = v; }
    function folder(f) { locfolder = f; }
    function makeTwo(v) { return ("0" + v).slice(-2); }
    function makeTwo(v) { return ("0" + v).slice(-2); }
    function makeThree(v) { return ("00" + v).slice(-3); }
    function getDateName() { var d = new Date(); var outstr = d.getFullYear() + makeTwo(d.getMonth() + 1) + makeTwo(d.getDate()) + "-" + makeTwo(d.getHours()) + makeTwo(d.getMinutes()) + makeTwo(d.getSeconds()) + "-"; return outstr; }
    function doNote() { filename = locfolder + datename + "N" + makeThree(curr_note) + "V" + makeThree(curr_vel) + ".wav"; outlet(1, filename); outlet(0, curr_note, curr_vel); }
    function start() { if (!is_running) { // set up the naming convention datename = getDateName(); post("Beginning sample run, name: " + datename + "*"); // set up the notes and get it rolling! curr_note = n_start; curr_vel = v_start; is_running = true; // output the note! doNote(); } }
    function next() { if (is_running) { curr_vel += v_interval; if (curr_vel > v_end) { curr_vel = v_start; curr_note += n_interval; } if (curr_note > n_end) { stop(); } else { doNote(); } } }
    function stop() { if (is_running) { is_running = false; outlet(2, "bang"); } }