Jump to content

How do I stop Scripter execution ?


fuzzfilth
Go to solution Solved by Dewdman42,

Recommended Posts

Ok, the final goal is to have a live triggerable, large, blinking Environment Button serving as a visual metronome inside Logic which is independent of Logic's Tempo and Transport state. My current solution is an EXS with 127 files of different tempo clicks, but this only gives me the tiny volume bar of the Audio Object to look at. Now I'm set on Scripter to pull this off in MIDI.

 

- It calculates 60 / (Note Number+60) * 1000, this returns milliseconds for 60-187 bpm right off the triggering keyboard. Fine so far

- It plays a NoteOn and a NoteOff 150ms later. Fine so far

- It repeats this NoteOn/Off in a FOR loop until count is ten (for testing, should be infinite, really), each time adding the previously calculated delay. Fine so far

- Its output feeds through External Instrument>IAC back into an Environment Button which blinks ten times. Fine so far.

 

Now. How do I stop this loop when the next key for another tempo comes along ? Currently it stubbornly plays its ten programmed repetitions over the new, different ones, which of course messes everything up. I have tried to insert a BREAK condition in the loop, but this doesn't work as the loop is not processed in real time. On the first note, the script is processed 'instantly' and the result is played at its scheduled time stamps. When the second note comes while the first note's result still plays, the first note's script is long gone, so there is nothing to stop.

 

Or is there ? Is there a way to flush this buffer, like, NOW ?

 

Christian

Link to comment
Share on other sites

Here it is. I've commented out everything that didn't work, and I've already deleted stuff that didn't work either...

 

var Delay = 500;
var Blink = 150;
var NeedsTimingInfo = true;
var stop = false


function HandleMIDI(e) {
 if (e instanceof ControlChange && e.number == 123){ //send AllNotesOff and set the Stop flag
  MIDI.allNotesOff()
  //stop= true
 }
 if (e instanceof NoteOn) { // Evaluate incoming Note
   //stop = true
   Delay = 60/(60+e.pitch)*1000;
   e.pitch = 60;           // set Pitch to C3
   for (var loop =0; 10 > loop;loop++){ 
     stop = false
     e.velocity = 127;     // acts as a NoteOn
     e.sendAfterMilliseconds(loop*Delay);   
     e.velocity = 0;      // acts as a Note Off
     e.sendAfterMilliseconds(loop * Delay + Blink);
     if (stop == true){
       //MIDI.allNotesOff()
       //stop = false;
       //break;
     }  
   }
 }
} 


function ProcessMIDI() {

   var info = GetTimingInfo();

 }

Link to comment
Share on other sites

Or is there ? Is there a way to flush this buffer, like, NOW ?

Christian

HandleMIDI() gives you MIDI Events to handle, it won't give you another MIDI Event until you finish processing the current MIDI Event, meaning, when you exit HandleMIDI()

 

So in your code, when you do your e.sendAfterMilliseconds() and then finally leave handleMIDI(), that's it, those notes are going to get processed.

 

I see what you want to do though, I'll do some testing here on a couple of things I have in mind.

Link to comment
Share on other sites

Try this and please let me know if you have any questions about how it works:

 

//======= Customization ======

var clickDuration = 150;
var clickPitch    = MIDI.noteNumber("C3");  // 60
var clickVelocity = 127;
var clickChannel  = 1;
var lowBpm        = 60;

//======= init variables =====

// Set up array of delay times in ms
var gap = [];
for(var pitch=0;pitch<128;pitch++) {
   gap[pitch] = lowBpm/(lowBpm+pitch)*1000;
}

var currentPitch = 0;
var metroActive = false;
var nextClick = 0;

//=========================
// callbacks
//=========================

function HandleMIDI(event) {

   // Look for AllNotesOff message to stop metronome
   if (event instanceof ControlChange 
           && event.number == 123){
           
       //send AllNotesOff
       MIDI.allNotesOff()
       metroActive = false;
       nextClick = 0;
       return;
   }

   // Look for a midi key to turn on metronome, pitch sets tempo    
   if (event instanceof NoteOn) {
   
       Trace("Received ["+MIDI.noteName(event.pitch)+
           "] Setting metronome to "+
           (event.pitch+lowBpm)+" BPM");
       
       // if not already playing metronome, send first click
       if(!metroActive) {
           // send first click right now
           sendClick();
           nextClick = Date.now()+gap[event.pitch];
       }
       
       // metronome is already playing, so skip first click, and 
       // recalculate the next one
       else {
           nextClick = nextClick - gap[currentPitch]
                              + gap[event.pitch];
       }
       
       currentPitch = event.pitch;
       metroActive = true;
   }
} 


function ProcessMIDI() {
   if(metroActive) {
       if(Date.now() > nextClick) {
           sendClick();
           nextClick = nextClick + gap[currentPitch];
       }
   }
}

//=========================
// support functions
//=========================

var metronome = new NoteOn;
metronome.pitch = clickPitch;
metronome.velocity = clickVelocity;
metronome.channel = clickChannel;

//====================
// send a single click
//====================
function sendClick() {
   metronome.velocity = clickVelocity;
   metronome.send();
   metronome.velocity = 0;
   metronome.sendAfterMilliseconds(clickDuration);
}

 

I do want to make the following comment about it. You specified above that you wanted this metronome to work REGARDLESS of the current transport state. So even if Logic/Mainstage is not currently playing, you want this metronome to be sending out the clicks. It is coded as such above, however, please realize that this is forcing Scripter to perform in real time, which is not garantee'd to be accurate. Some clicks might be missing or late. It seems to work pretty good though, its probably accurate within a ms or two, which is probably good enough. However if Logic were performing other tasks while doing this, it may or may not be as accurate due to the realtime nature of the script.

 

A better solution would schedule the clicks at least one click into the future so that they will be played on time without so much reliance on realtime response from Scripter. But such a script is a little more complicated.

 

Stay tuned..

Edited by Dewdman42
Link to comment
Share on other sites

  • Solution

here is a better version...this version schedules the click events, one-click into the future so that they should be more rock solid in terms of timing. They will not be reliant on the script being able to execute in true real time. Its a bit more complicated then the first one, so study the first one to learn, maybe the second one for use...and to learn too if you want. 8-)

 

pitchMetronome.pst.zip

 

//
// Independent metronome, BPM based on pitch
//

//======= Customization ======

var clickDuration = 150;
var clickPitch    = MIDI.noteNumber("C5");
var clickVelocity = 127;
var clickChannel  = 1;
var lowBpm        = 60;

//======= init variables =====

// Set up array of delay times in milliseconds
var gap = [];
for(var pitch=0;pitch<128;pitch++) {
   gap[pitch] = lowBpm/(lowBpm+pitch)*1000;
}

var metroActive = false;
var currentPitch;
var nextClick;

//=====================
// callbacks
//=====================

function HandleMIDI(event) {
       
   now = Date.now();

   //===============================================
   // Look for AllNotesOff message to stop metronome
   //===============================================
   
   if (event instanceof ControlChange 
           && event.number == 123) {
       //send AllNotesOff
       MIDI.allNotesOff()
       metroActive = false;
       nextClick = 0;
       return;
   }

   //===============================================
   // Look for a midi key to turn on metronome, 
   // pitch sets tempo    
   //===============================================
   
   if (event instanceof NoteOn) {
   
       Trace("Received ["+MIDI.noteName(event.pitch)+
             "] Setting metronome to "+
             (event.pitch+lowBpm)+" BPM");
       
       // If metro not yet active send first click 
       // and also schedule next one
       if(!metroActive) {
           sendClick(0);
           sendClick(gap[event.pitch]);
           nextClick = now + gap[event.pitch];
           metroActive = true;
       }
       
       // metro is already playing, skip first click, since
       // the next click is already scheduled
       // and calculate best place for future second one
       else if (now > nextClick) {
           var futureClick = nextClick + gap[event.pitch];
           sendClick(futureClick-now);            
       }
       
       currentPitch = event.pitch;
   }
} 


//====================================================
// if metronome currently playing, send a click only if needed
//====================================================
function ProcessMIDI() {
   
   if(metroActive) {
   
       var now = Date.now();
       
       // if we have passed the time of the next click
       // schedule the next one in the future
       if( now > nextClick ) {
           nextClick = nextClick+gap[currentPitch];
           sendClick(nextClick-now);
       }
   }
}

//========================
// support functions
//========================

var metronome = new NoteOn;
metronome.pitch = clickPitch;
metronome.channel = clickChannel;
metronome.velocity = clickVelocity;

//====================
// send a single click
//====================
function sendClick(msDelay) {
   metronome.velocity = clickVelocity;
   metronome.sendAfterMilliseconds(msDelay);
   metronome.velocity = 0;
   metronome.sendAfterMilliseconds(msDelay+(msDelay*0.333));
}
Edited by Dewdman42
Link to comment
Share on other sites

Ok, this works a treat. Thanks for extensive documentation and comments, this helped a lot. I modified it slightly. I let the on-phase note always be 1/3 on and 2/3 off, this scales better over fast and slow tempos, and I added a Transformer behind it (couldn't resist), to clean up the double triggers that tend to occur when switching tempos.

 

This is perfect.

 

Thanks again for helping me this much.

 

Christian

Link to comment
Share on other sites

glad to hear you were able to tweak things to your benefit.

 

Regarding the double ones, it shouldn't be doing that I'd like to fix the script if it is...do you have any idea of some specific use situations when that occurs? If anything I coded it so that its a little slow to change tempos in order to avoid that...

Link to comment
Share on other sites

Regarding the double ones, do you have any idea of some specific use situations when that occurs?

 

It's easy to recreate. Replace the External Instrument (which only is used to get MIDI out of the Scripter and back into Logic) by a Klopfgeist and set the script's clickPitch two octaves higher to 84, the Klopfgeist's normal pitch. Then play a couple of notes in series and you'll hear it. More often than not, two Note Ons occur at once, resulting in that familiar phased sound. Since this happens without Ext Inst and IAC, it's something in the script. My Monitor Object tells me there 's two Note Ons in succession but just one Note Off, for whatever reason.

 

Anyway, no need to go out of your way for this, setting up the Transformer to filter the duplicate events was literally a four-click affair and I'm happy.

 

Thanks once more.

Edited by fuzzfilth
Link to comment
Share on other sites

I actually think right now maybe the problem is more that you are feeding your midi controller into both the script as well as directly to the Klopfgeist...which is why you get the phase canceled sound.. The script should be filtering that out, but I think if you change your midi controller setup in the environment to make sure it doesn't route that way that should solve the problem without a transformer?
Link to comment
Share on other sites

Here's your slightly modified script, changes are in line 8 where clickPitch is now set to 84 and line 113 where clickDuration is now msDelay*0.333. Changing it back to the original does not change this.

 

//
// Independent metronome, BPM based on pitch
//

//======= Customization ======

var clickDuration = 150;
var clickPitch    = MIDI.noteNumber("C5");  // 84
var clickVelocity = 127;
var clickChannel  = 1;
var lowBpm        = 60;

//======= init variables =====

// Set up array of delay times in milliseconds
var gap = [];
for(var pitch=0;pitch<128;pitch++) {
   gap[pitch] = lowBpm/(lowBpm+pitch)*1000;
}

var metroActive = false;
var currentPitch;
var nextClick;

//=====================
// callbacks
//=====================

function HandleMIDI(event) {
       
   now = Date.now();

   //===============================================
   // Look for AllNotesOff message to stop metronome
   //===============================================
   
   if (event instanceof ControlChange 
           && event.number == 123) {
       //send AllNotesOff
       MIDI.allNotesOff()
       metroActive = false;
       nextClick = 0;
       return;
   }

   //===============================================
   // Look for a midi key to turn on metronome, 
   // pitch sets tempo    
   //===============================================
   
   if (event instanceof NoteOn) {
   
       Trace("Received ["+MIDI.noteName(event.pitch)+
             "] Setting metronome to "+
             (event.pitch+lowBpm)+" BPM");
       
       // If metro not yet active send first click 
       // and also schedule next one
       if(!metroActive) {
           sendClick(0);
           sendClick(gap[event.pitch]);
           nextClick = now + gap[event.pitch];
           metroActive = true;
       }
       
       // metro is already playing, skip first click, since
       // the next click is already scheduled
       // and calculate best place for future second one
       else {
           var futureClick = nextClick + gap[event.pitch];
           sendClick(futureClick-now);            
       }
       
       currentPitch = event.pitch;
   }
} 


//====================================================
// if metronome currently playing, send a click only if needed
//====================================================
function ProcessMIDI() {
   
   if(metroActive) {
   
       var now = Date.now();
       
       // if we have passed the time of the next click
       // schedule the next one in the future
       if( now > nextClick ) {
           nextClick = nextClick+gap[currentPitch];
           sendClick(nextClick-now);
       }
   }
}

//========================
// support functions
//========================

var metronome = new NoteOn;
metronome.pitch = clickPitch;
metronome.channel = clickChannel;
metronome.velocity = clickVelocity;

//====================
// send a single click
//====================
function sendClick(msDelay) {
   metronome.velocity = clickVelocity;
   metronome.sendAfterMilliseconds(msDelay);
   metronome.velocity = 0;
   metronome.sendAfterMilliseconds(msDelay+(msDelay*0.333)); // was clickDuration
   }

 

I have a VI channel with Scripter and Ext Inst. as the active track in the Arrangement to be able to test from the keyboard (this will be replaced a triggered Text fader later, but I digress...). The Ext Inst goes out via IAC, back into Logic's Physical In (tapping that very IAC stream with a Cable and thus removing it from the SUM of the Physical In) into a Monitor and into Klopfgeist. Anyway, it's doing that even with a Klopfgeist directly in the VI channel and no IAC routing at all.

 

All other clicks are ok, it's jut those where the tempo switches which are doubled. So if things are doubled, I tend to think it's the script doing that.

Link to comment
Share on other sites

Take a look carefully at your environment cabling. Keep in mind that by having Scripter sitting on a track, then if that track is selected, the output from it is feeding back into itself.

 

Its probably better in general to create a mixer channel object in the environment as Inst255 and put scripter+ExtInst on that mixer channel, disassociated from any arrange page tracks. Then you cable your midi controller to that mixer channel object in order to select the tempos from the keyboard.

 

The other issue is whether your midi controller is somehow being routed directly to Klop. Can you send a screen shot of your environment setup?

 

In any case, I do not believe the Script itself is sending the double clicks...its routing related... The script was already designed to make it very smooth to change tempos without such things...but the environment can be a bit tricky in cabling midi around.

Link to comment
Share on other sites

Ok, I stripped everything down to the bare essentials:

 

http://www.birgit-obermaier.de/Media/doubles.png

 

- Keyboard Object feeds into Inst 1 with Scripter and Klopfgeist

- Track Audio 1 is selected as the active track in the Arrangement

- The chain PhysicalIn>Keyboard>Monitor>SequencerIn is idle

- Doubles do happen when switching tempos by cklicking the Keyboard Object that's connected to Inst 1. All of this in a new and empty project.

 

As i said, I'm happy with it as is, it does everything I want.

 

Cheers

Link to comment
Share on other sites

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.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...