killersolos Posted August 31, 2013 Share Posted August 31, 2013 Attached is Pitchcaster, a MIDI FX plug-in script for Logic Pro X. You start in Learn mode and you play something to it and it learns the notes that are played. Then you switch to Run it and it'll take whatever is played and keep the note ons and offs and velocities and controller movements and so forth and it'll randomly replace the pitches of the notes according according to the distribution of pitches that were learned during the learning phase. It blows away your normal MIDI randomizers because the notes that it chooses are distributed only among the notes that it was taught. I've done a video that thoroughly demonstrates the plugin including doing many musically interesting variations off a very famous riff. I am a pretty hardcore programmer and I've tried to write a program like this off and on for a decade and failed until LPX came out with the scripting interface and it took me all of two evenings to get it working. If you're interested, please try it and let me know what you think. /* Killer Solos Pitchcaster v1.0 Beware MIDI loops. Watch the video, YouTube user bitwonk. Copy this file by doing option-A, option-C in Text Editor. Then in Logic add a MIDI effect to a mixer channel and select scripter. Hit Save As... Killer Solos Pitchcaster. Then click on Open Script in Editor and in the editor, select all (option-A) and hit backspace to wipe out what's there, hit option-V to paste in the program, and off you go. Sorry this is clumsy but it's easier to deal with programs as text and the format Logic uses to save scripts into isn't text. Eventually there should be something better. */ /* Copyright (c) 2013, Karl Lehenbauer All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // include file learner-class.js // // Copyright © 2013 Karl Lehenbauer. All Rights Reserved. // // Released to the public under the Berkeley copyright. // // Redistribution and use, with or without modification, is permitted // provided the copyright notice is maintained. // // see the LICENSE file for details // // // Learner class // // invoke learn method repeatedly with pitches and the Learner class will // count them, learning what notes were struck and how often // // Creating an instance of the Learner class also instances an instance of // the ProbabilityVector class, which Learner's genprobos method will load // up with an object for each note played during the learning period, with // the note numbers augmented by the count of that note and a ratio that // is used for randomly picking notes with the same distribution. // // the pick_note() method of the ProbabilityVector class, once the Learner // and learned and its knowledge passed to the ProbabilityVector, will return // random note numbers distributed at about the same rate as during the // learning period. // function Learner() { this.keyboard = new Array(128); this.probo = new ProbabilityVector(); this.reset(); } Learner.prototype.reset = function() { for (var i = 1; i <= 128; i++) { this.keyboard[i] = 0; } this.notesLearned = 0; this.uniqueNotesLearned = 0; this.probo.reset(); Trace('learner object reset: ' + this); } // learn - learn a note Learner.prototype.learn = function (pitch) { if (this.keyboard[pitch] == 0) { this.uniqueNotesLearned++; } this.keyboard[pitch]++; this.notesLearned++; } // sum - calculate the number of notes learned Learner.prototype.sum = function () { var sum = 0; for (var i = 1; i <= 128; i++) { sum += this.keyboard[i]; } return(sum); } // genprobos - generate data into the probability vector Learner.prototype.genprobos = function () { this.probo.reset(); // find all the keys that had notes played on them // and stick them into the probability vector object // we created when we were instantiated for (var i = 1; i <= 128; i++) { if (this.keyboard[i] == 0) { continue; } this.probo.addprobo(i, this.keyboard[i]); } this.probo.calc_ratios(); } // pick_note - helper function to simplify invoking the pick_note // function of the probability ector Learner.prototype.pick_note = function () { return this.probo.pick_note(); } // // ProbabilityVector class // // invoke addprobo method with note numbers and counts for each note // // invoke calc_ratios() to prep it // // invoke pick_note() to get note numbers // function ProbabilityVector() { this.reset(); } // reset - clear the probability vector ProbabilityVector.prototype.reset = function () { this.vector = []; this.sum = 0; } // sum - get a note count of learned notes from the probability vector ProbabilityVector.prototype.sum = function () { var sum = 0; for (var i = 0; i < this.vector.length; i++) { sum += this.vector[i].count; } return (sum); } // create an object with a note and count and push it onto the vector ProbabilityVector.prototype.addprobo = function (note, count) { var probo = new Object(); probo.note = note; probo.count = count; this.vector.push(probo); this.sum += count; } // calc_ratios - run through each note object in the probability vector // and add a ratio element that transits 0.0 to 1.0 // for each note's probability of being chosen ProbabilityVector.prototype.calc_ratios = function () { var sofar = 0.0; for (var i = 0; i < this.vector.length; i++) { sofar += (this.vector[i].count / this.sum); this.vector[i].ratio = sofar; } } // pick_note - pick a note following the desired distribution ProbabilityVector.prototype.pick_note = function () { var wantRatio = Math.random(); // logger('wantRatio', wantRatio); for (var i = 0; i < this.vector.length; i++) { // logger('this ratio', this.vector[i].ratio); if (wantRatio < this.vector[i].ratio) { return this.vector[i].note; } } } logger = function (string1, string2) { return; var s = new String(); Trace(s.concat(string1, ":", string2)); } // vim: set ts=4 sw=4 sts=4 noet : if (0) { x = new ProbabilityVector(); x.addprobo(60,1); x.addprobo(61,3); x.addprobo(65,5); x.addprobo(71,1); x.addprobo(73,6); x.addprobo(79,2); x.calc_ratios(); } // include file synthetickeyboard.js // // SyntheticKeyboard class - has methods to turn on and off synthetic keys, // and to query to see if a synthetic key is on or not. // // Also the fetch and store methods provide a way for arbitrary stuff to // be associated with a pitch (like the pitch of another key that the key // was translated to) // // define a SyntheticKeyboard object. // // Reset it with the reset method. // // store a value for any pitch using store and fetch it using fetch. // // the convenience functions note_on, note_off, and is_note_on are // provided for common use. // function SyntheticKeyboard () { this.keyboard = new Array(128); this.reset(); } SyntheticKeyboard.prototype.reset = function () { for(var i = 1; i <= 128; i++) { this.keyboard[i] = 0; } } SyntheticKeyboard.prototype.store = function (pitch, value) { this.keyboard[pitch] = value; } SyntheticKeyboard.prototype.fetch = function (pitch) { return this.keyboard[pitch]; } SyntheticKeyboard.prototype.note_on = function (pitch) { this.store(pitch,1); } SyntheticKeyboard.prototype.note_off = function (pitch) { this.store(pitch,0); } SyntheticKeyboard.prototype.is_note_on = function (pitch) { return this.fetch(pitch); } // main script pitchcaster.js // // Killer Solos Pitchcaster // // NeedsTimingInfo = true; var noteCounter = 0; var minNotesToLearn = 4; var learner = new Learner(); var syntheticKeyboard = new SyntheticKeyboard(); var keyTranslations = new SyntheticKeyboard(); function Reset() { Trace('big Reset invoked'); syntheticKeyboard.reset(); keyTranslations.reset(); } Reset(); function HandleMIDI(event) { var mode = GetParameter('Mode'); // if in learn mode and it's a note on, learn it. // also if it's not in learn mode but it has learned less // than 4 notes, learn it if (event instanceof NoteOn) { if (mode === 0 || learner.uniqueNotesLearned < minNotesToLearn) { learner.learn(event.pitch); // if it's not in learn mode and it now knows minNotesToLearn notes, // generate the probability vector if (mode != 0 && learner.uniqueNotesLearned == minNotesToLearn) { learner.genprobos(); Trace('generated probos due to minNotesToLearn'); } } } // if in Learn or Standby or it's in Run but hasn't learned at least // minNotesToLearn notes, send the event downstream and we're done if ((mode === 0) || (learner.uniqueNotesLearned < minNotesToLearn)) { event.send(); return; } // OK, we're in run mode. // if it's not a note on or note off, send it along and we're done if (!(event instanceof NoteOn) && !(event instanceof NoteOff)) { event.send(); return; } // ok, we're running and we know it's a note on or note off. if (event instanceof NoteOn) { // decide if we are going to change the note if (!ChangeIt()) { var wantNote = event.pitch; // logger('keep', wantNote); } else { var wantNote = learner.pick_note(); // logger('new', wantNote); } var success = 0; for (var i = 0; i < 20; i++) { if (!syntheticKeyboard.is_note_on(wantNote)) { success = 1; break; } wantNote = learner.pick_note(); // logger('repick', wantNote); } // if we tried and tried and never found a playable note, bail // NB not sure about this if (!success) { return; } // we picked the note, keep track of the translation syntheticKeyboard.note_on(wantNote); keyTranslations.store(event.pitch, wantNote); event.pitch = wantNote; event.send(); return; } // ok it's a note off, translate it to what we translated it to, // which may be the same as what we start with, send a note off // for that, and clear the note on the synthetic keyboard if (event instanceof NoteOff) { // logger('>off',event.pitch); var pitch = keyTranslations.fetch(event.pitch); keyTranslations.store(event.pitch,0); // logger('off',pitch); event.pitch = pitch; event.send(); syntheticKeyboard.note_off(pitch); } } function ChangeIt() { return (Math.random() < (GetParameter('Change') / 100.0)); } function ParameterChanged(param, value) { Trace('parameter ' + param + ' changed to ' + value); if (param === 0) { if (value === 0) { learner.reset(); } // if transitioned to standby or run, they might ended learn mode, // generate the probably vector if (value > 0) { // if they turned off learner mode (turned on run mode), // generate the probability vector learner.genprobos(); Trace('generated probos'); } } } var PluginParameters = [ {name:"Mode", type:'menu', valueStrings:["Learn", "Run"], defaultValue:0}, { name:'Change', type:'lin', unit:'percent', minValue:0, maxValue:100, numberOfSteps:100, defaultValue:50}, ]; Quote Link to comment Share on other sites More sharing options...
parkcomm Posted August 28, 2014 Share Posted August 28, 2014 You had me up the point you said Life in the Fast Lane was one of the best songs ever written This is genius, well done. Quote Link to comment Share on other sites More sharing options...
virag0 Posted August 28, 2014 Share Posted August 28, 2014 This even works in Mainstage. I loaded it up, played some notes in and it transformed the sequence I was sending from my Octopus. In the case of Mainstage, being able to load a riff from a file either as a MIDI file or note array would be interesting. Thanks for such an interesting tool. I am restricting myself to Mainstage and doing most of my work on the hardware side, so this makes it very interesting to jam with! rachel Quote Link to comment Share on other sites More sharing options...
airforceguitar Posted March 29, 2015 Share Posted March 29, 2015 Really nice work killersolos! Makes me want to learn my JavaScript! Quote Link to comment Share on other sites More sharing options...
angelonyc Posted August 9, 2015 Share Posted August 9, 2015 The script I downloaded doesn't show a learn or play function.. nor does it show degree of variation to include.. Did I miss somthing.. I would TRULY LOVE TO USE THIS.. Mark Styles Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.