INDEPENDENT TRACK LENGTHS: a new feature for the open-source project The Amanuensis: Automated Songwriting and Recording

in #utopian-io5 years ago (edited)

amanuensis final-03.png
logo by @camiloferrua

Repository

https://github.com/to-the-sun/amanuensis

The Amanuensis is an automated songwriting and recording system aimed at ridding the process of anything left-brained, so one need never leave a creative, spontaneous and improvisational state of mind, from the inception of the song until its final master. The program will construct a cohesive song structure, using the best of what you give it, looping around you and growing in real-time as you play. All you have to do is jam and fully written songs will flow out behind you wherever you go.

If you're interested in trying it out, please get a hold of me! Playtesters wanted!

New Features

  • What feature(s) did you add?

The Amanuensis is a multitrack looper that loops over the entire duration of a growing song, written by you, but arranged by the machine. Previously each loop was always the same length, so if your song was 2 minutes long and you wanted drums throughout, you would have to play them for 2 minutes to fill the song. This is largely how the system should and still does work, but now you also have the option of leaving your captured drum (or any other) track short and allowing it to loop repeatedly while the other longer tracks play out.

Here's the resulting song from the demo above, automatically uploaded to SoundCloud once produced:

With this strategy songs can coalesce much more quickly and a steadier rhythm to brace against while playing helps keep additional tracks more rhythmic as well. In longer songs, whole verses and choruses will emerge spontaneously as longer loops repeat back over more complex, varied material.

I had been toying around with this functionality in my mind for a while, but it always seemed that it would mean every track would be filled with notes and begin pigeonholing all of your songs in a more "electronic" direction. Short loops do tend to do this, but the option is still always there to grow those loops into something more, as, unlike a traditional looper, there is no limit to the length of the looping material. You can still also add as much silence as you want to a track. Just jump back into an old track when the larger loops are at a later point in the song and all the space on that track between your new addition and your original small looping section will be filled with silence.

  • How did you implement it/them?

If you're not familiar, Max is a visual language and textual representations like those shown for each commit on Github aren't particularly comprehensible to humans. You won't find any of the commenting there either. Therefore, the work completed will be presented using images instead. Read the comments in those to get an idea of how the code works. I'll keep my description here about the process of writing that code.

These are the primary commits involved:

At first I thought I was going to have to duplicate many different portions of code 16 times to handle the 16 possible different-length tracks. The optimal strategy in the end was to take the main signal generated by progression.gendsp and apply a modulo operation to it. This needed to be done on 16 different signals and an individual seq~ was needed for each to drive, but this was all that needed to be multiplied by 16.

To accomplish this, the new mc (multichannel) functionality of Max 8 was utilized, which allows single patch cords to contain many channels of signals. The objects connected by these patch cords also take on the appropriate number of instances, something that otherwise would have required a huge mess and quite possibly a poly~.


the new p mc.seq~ in organism.maxpat, neatly replacing the old single seq~ in p midiPalette, complete with commenting

I contemplated filling each instance of the mc.seq~ with only cues from the corresponding track, which would be less processing intensive, but at far greater complexity. In the end it worked well to simply have the full repertoire of cues in every instance and simply filter them by voice number as they exited the object.

The next task was to determine the different changing lengths of each track and convey them to the one side of the modulo operation. 16 samples in a new buffer~, beats_by_track, were allotted to hold these values and the code below constantly extracts them.


the contents of the above mc.gen~ @chans 16 @usebusymap 0, complete with commenting

This buffer~ is filled with values by the following code, which occurs immediately after new spans are added to the song.


p grow_song? in organism.maxpat, complete with commenting

the contents of the above gen, complete with commenting

This functionality then needed to be conveyed on the GUI, with individual playheads for each track. The process was more complex than it first appeared, but it boiled down to the following. First, the playhead object was split into an array with indices for each track.


for(var i = 1; i <= tracks; i++) {
    // create a [jit.gl.gridshape] object to act as a playhead[i]
    playhead[i] = new JitterObject("jit.gl.gridshape","ListenWindow");
    playhead[i].shape = "plane";
    playhead[i].dim = [2,2];
    playhead[i].poly_mode = [1,1];
    playhead[i].color = beige_border;
    playhead[i].blend_enable = 1;
    playhead[i].position = [0, 2*(tracks-(i-0.5))/tracks-1];
    playhead[i].scale = [0, track_height];
    playhead[i].layer = 8; // highest value means object closest to viewer.
    playhead[i].enable = 1; // not rendered (unless playhead[i]Enable(1) called)
}

Then the function that handles the playhead position was updated accordingly, taking into account vertical position as well.

function playBeat(i, beat) {
    playhead[i].position = [-aspectratio+(2*aspectratio*(beat-timeline_offset)/total_length), 2*(tracks-(i-0.5))/tracks-1]; // ... or the other
    playhead[i].enable = 1; //any moving playhead should be visible
}

snapshot~s of the 16 moduloed ramps are taken and given to the JavaScript.


the new playhead subpatcher in timeline.maxpat, complete with commenting

This next commit addresses both a long-standing issue and something that became more pertinent with this latest update. It had to do with the song starting at beat 1 rather than 0.

After applying the modulo operation, tracks would start out at beat 1, but loop back to beat 0, causing inconsistencies in rhythm.

It had also been determined long ago that the very first cue placed in the song and others, beginning exactly at beat 1, are often missed because the DSP ramp triggering them is not always accurate enough to actually pass through 1 itself. While working on these updates, this was reconfirmed.

Therefore it was decided to make a modification to the following two lines of code in progression.gendsp, starting the song at 0.

ramp_base = 0;                                      
…
ramp = ramp_base;// - (sampstoms(vectorsize * 2) / click);  //start ramp a vector early so cues at 1 always trigger

Various adjustments were also required elsewhere throughout the program to account for this 1-beat difference, including modifying the earliest any cue can begin to 0.5.

GitHub Account

https://github.com/to-the-sun

To see a full history of updates, blog posts, demo songs, etc., check out my Steemit blog @to-the-sun.


Until next time, farewell, and may your vessel reach the singularity intact

To the Sun

Sort:  
  • Good article with sound clip, images, explanation of coding choices and a video.
  • The commits comments could be more extensive. See a pattern?

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Chat with us on Discord.

[utopian-moderator]

What do you mean by the commits comments?

These are the comments you've put on your commits. Basically a title.

Here are comments from another project that goes into more details. You can use the text from your article or vice versa.

Thank you for your review, @helo! Keep up the good work!

Hey, @to-the-sun!

Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Coin Marketplace

STEEM 0.29
TRX 0.11
JST 0.033
BTC 63458.69
ETH 3084.37
USDT 1.00
SBD 3.99