fuzzfilth Posted October 26, 2018 Share Posted October 26, 2018 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 Quote Link to comment Share on other sites More sharing options...
Atlas007 Posted October 26, 2018 Share Posted October 26, 2018 Perhaps submitting here your code could let the savvy ones have a look at it. In the meantime, I'd blindly venture that your algorythm loop (?) should imbed both the triggering / handling / and beat emitting processes. Quote Link to comment Share on other sites More sharing options...
fuzzfilth Posted October 26, 2018 Author Share Posted October 26, 2018 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(); } Quote Link to comment Share on other sites More sharing options...
Dewdman42 Posted October 26, 2018 Share Posted October 26, 2018 When I get home I will make you something Quote Link to comment Share on other sites More sharing options...
ValliSoftware Posted October 26, 2018 Share Posted October 26, 2018 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. Quote Link to comment Share on other sites More sharing options...
Dewdman42 Posted October 26, 2018 Share Posted October 26, 2018 (edited) 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 October 27, 2018 by Dewdman42 Quote Link to comment Share on other sites More sharing options...
Solution Dewdman42 Posted October 27, 2018 Solution Share Posted October 27, 2018 (edited) 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. 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 October 27, 2018 by Dewdman42 Quote Link to comment Share on other sites More sharing options...
fuzzfilth Posted October 27, 2018 Author Share Posted October 27, 2018 Wow. Thank you. Thank you very much. I will work my way through it and see how that goes. Quote Link to comment Share on other sites More sharing options...
fuzzfilth Posted October 27, 2018 Author Share Posted October 27, 2018 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 Quote Link to comment Share on other sites More sharing options...
Dewdman42 Posted October 27, 2018 Share Posted October 27, 2018 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... Quote Link to comment Share on other sites More sharing options...
fuzzfilth Posted October 27, 2018 Author Share Posted October 27, 2018 (edited) 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 October 27, 2018 by fuzzfilth Quote Link to comment Share on other sites More sharing options...
Dewdman42 Posted October 27, 2018 Share Posted October 27, 2018 I will try to fix it, I can't sleep otherwise. is it going that with the scripts I posted or just with the one you modified? Can you share your modified one? Quote Link to comment Share on other sites More sharing options...
Dewdman42 Posted October 27, 2018 Share Posted October 27, 2018 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? Quote Link to comment Share on other sites More sharing options...
fuzzfilth Posted October 27, 2018 Author Share Posted October 27, 2018 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. Quote Link to comment Share on other sites More sharing options...
Dewdman42 Posted October 27, 2018 Share Posted October 27, 2018 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. Quote Link to comment Share on other sites More sharing options...
Dewdman42 Posted October 27, 2018 Share Posted October 27, 2018 hmm, I am getting double clicks now too, but only sometimes... If I figure it out I'll let you know.. otherwise, enjoy whatever works... Quote Link to comment Share on other sites More sharing options...
fuzzfilth Posted October 27, 2018 Author Share Posted October 27, 2018 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 Quote Link to comment Share on other sites More sharing options...
Dewdman42 Posted October 27, 2018 Share Posted October 27, 2018 found it. Changed the following lines: else if (now > nextClick) { var futureClick = nextClick + gap[event.pitch]; sendClick(futureClick-now); } I have replaced the script in the above post with the fixed version. give that a try, you shouldn't need transformer now. Quote Link to comment Share on other sites More sharing options...
Dewdman42 Posted October 27, 2018 Share Posted October 27, 2018 Aside from the fact that you now have a working script, is there any interest in having a discussion about why the original script you posted didn't work and why this one does? For the sake of learning? Quote Link to comment Share on other sites More sharing options...
fuzzfilth Posted October 27, 2018 Author Share Posted October 27, 2018 Yes, but now I'm tired. I have been working with sound for the past 17 hours, including but not limited to clicks on keyboards ;o) Be back tomorrow... 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.