dcer10 Posted November 3, 2006 Report Share Posted November 3, 2006 Hi All,How hard would it be to code up an application that simply was a master midi clock with a stop pause play button set which would accept midi notes via the input but delay them until the next beat/bar etc? The main purpose for this would be for devices with an arrange mode like the Kurzweil K series synths where you can store a whole bunch of songs as midi files then from one channel you can trigger them off in real time as an arrangement which can also be recorded as a seperate midi file. Currently there is no way I know how to "beat sync" a note from a midi keybaord to the midi clock. I know this can be done easily in software, or indeed by hand editing the arrange sequence in the Kurzweil, but the main purpose for this would be to be able to play the arrange mode of the K rack in a live situation without having the chance for the songs to go out of sync.This method would only want to look for incoming notes as other controls would not want to be synced with the clock ie midi CC's sysex etc. Ideally it would be awesome if this box could also do the same acting as a sync slave too.Im sure there would be other applications for this box than my specific need, playing in time selectable to quantisze your notes to the 1/16th etc for new keyboard players would be one of them. I could imagine this as a cool MIOS toy that may be of interest to people too?Any ideas would be cool :}Im hoping to learn some C soon which could be used for this, but im a long way from knowing where to start! Is C even suitable for such a timing intensive operation?Thanks,John Quote Link to comment Share on other sites More sharing options...
Mr modnaR Posted November 3, 2006 Report Share Posted November 3, 2006 i can't help you other than to say: that's a great idea! ;D (if you need any design work doing on the case, though, i'm just a PM away!) Quote Link to comment Share on other sites More sharing options...
mess Posted November 3, 2006 Report Share Posted November 3, 2006 I think this would be doable in Cyou could use the clockbox application as an exampleif you need help with the coding let me know...Michaël Quote Link to comment Share on other sites More sharing options...
audiocommander Posted November 3, 2006 Report Share Posted November 3, 2006 ...which would accept midi notes via the input but delay them until the next beat/bar etc?That's exactly what I'm currently developing ;D...but I'm not planning to build it as Master Box, but rather as Slave, so the Signals are being passed when there's no clock-signal available and quantized when a signal is detected (the detection already works).I'm also planning to implement a function that converts CC#s to Notes, because the Quantisation provides a method to implement lengths of notes that is needed to send the appropriating note-off signals.I already checked out all the clock-stuff, but still need to think about the event-buffer. I programmed this already on the mac (Objective-C), and used a lot of pointers and arrays there, but I think MIOS needs kind of different approach to that...Cheers :DMichael Quote Link to comment Share on other sites More sharing options...
dcer10 Posted November 3, 2006 Author Report Share Posted November 3, 2006 Amazing that you also wanted the same thing! Small world :}People in sync - excuse the lame pun!Well I cant program to save my life, but if there is anything at all I can do to help move it along, please let me know. Next year I start studying electronics properly and hope to some day program and design PCBs but until them im a dreamer.All the best,John Quote Link to comment Share on other sites More sharing options...
stryd_one Posted November 3, 2006 Report Share Posted November 3, 2006 I already checked out all the clock-stuff, but still need to think about the event-buffer. I programmed this already on the mac (Objective-C), and used a lot of pointers and arrays there, but I think MIOS needs kind of different approach to that...Welcome to my Hell ;D I would be VERY interested in your thoughts on this subject at the moment mate... My inexperience with C is becoming very obvious to me on this subject... Quote Link to comment Share on other sites More sharing options...
mess Posted November 3, 2006 Report Share Posted November 3, 2006 would a circular buffer do the job?you could implement this without using pointers, just arraysdon't now about the performance difference between using pointers and arrays...Michaël Quote Link to comment Share on other sites More sharing options...
audiocommander Posted November 3, 2006 Report Share Posted November 3, 2006 Yeah, I appreciate talking about that stuff, I always feel like I got some holes in my brain whenever I try to think four-dimensional ::)Hm, at first, I'd have to decide how accurate everything should be. Because my personal encouragement is to do indetermined live-music, I'd prefer a simple solution and not one where I can fire thousands of notes in complete predetermined accuracy... if you know what I mean ;) Keeping some bytes for other things would be nice...Next, we need to think about what to store. If we schedule midi messages, we need to store at least three different numbers: type+ch, pitch, velocity... that doesn't help to make things easier... It would be nice to be able to store all kinds of messages (eg. also PRG-CH, CCs as well as Note-offs) and not only note-ons...So, what about the timing?The MidiClock signal is sent 96 times per bar. How does that look practically:1/1 = 96th (1 time)1/2 = 48th (2 times)1/4 = 24th (4 times)1/8 = 12th (8 times)1/16 = 6th (16 times)1/32 = 3rd (32 times)so, we can't use 1/64's anyway, because we would need to determine 1.5 clocks and – to be honest – that's a bit too much pain in the a** to me for a thing I am not able to hear... at least when one's not jamming at 20 bpm ;D haha... what an exiting jam! :Pthen we got the uneven notes:1/3 = 36th (3 times)1/6 = 18th (6 times)1/12 = 9th (12 times)So, there would be 9 arrays in total + 2*9 arrays for the corresponding pitch/velo information. Would be 27 arrays... uff...Anyway, another advantage would be, that the 1/1 array can be really small, whereas the maximum available length for arrays should be more than enough for the 1/32 array...But not finished here: I'd introduce 9 vars to keep track of the current fill-status (option 1). E.g. if there are 4 1/16 notes in the buffer, the var contains a 4, so that we know we can access index 0 - 3 when the firing-time is there. Afterwards we can set this var to 0.Another option (option 2) would be to fire only if the value > 0 and set the value to 0 afterwards. This would require to loop through the whole array each clock which might be not the most elegant solution, so I'd go for option 1.Besides we would exactly know where to add new buffer-elements, when there's a tracking var available. And we'd know when the buffer is full and deny further elements.I'd do all the scheduling whenever a message is received. The parameters (why what is scheduled in which array can be setup independently from the HUI and is worth another much too long post ;)What do you think?Best,AC Quote Link to comment Share on other sites More sharing options...
stryd_one Posted November 3, 2006 Report Share Posted November 3, 2006 *Brain explodes*WARNING - Do not read AC's post right after you wake up ;D I think I'll have to do that again when I'm slightly more conscious hehehe Quote Link to comment Share on other sites More sharing options...
audiocommander Posted November 4, 2006 Report Share Posted November 4, 2006 LOL ;DMaybe it's because it's slightly unlogic...I overlooked that I want to schedule also generated NoteOff events, which would be complicated this way; the idea behind this was not to generate a lookup array (fire event in buffer at time x), rather than a few simple buffers (like drawers), where you just sort out the events (sort into drawers) and fire the event (empty the drawer) when it's time...In other words: the current implementation (objective-c) uses modulus operators and a multidemensional array and consists therefore of a ridicilously low number of lines... but I see some problems in implementing for MIOS:- no huge arrays- no modulus (at least not cheap to have)- problems with nested, more-dimensional arraysokay, I think, I need to go to bed, before I'm confusing myself ;DCheers!Michael(see you at #midibox @efnet?, 've been there today :) ) Quote Link to comment Share on other sites More sharing options...
mess Posted November 4, 2006 Report Share Posted November 4, 2006 Hi AC,could you post the objective-C code?maybe we can get around the limitations somehowIt would also illustrate how your system works as I don't get it completely right now ::)Michaël Quote Link to comment Share on other sites More sharing options...
audiocommander Posted November 4, 2006 Report Share Posted November 4, 2006 sure,these are some snippets, that should illustrate how it works;it's not very cleaned up :-\ #pragma mark - #pragma mark m4 time intervals #define ZSTIMEINTERVAL_MAX 11 // timeinterval = ( 1 / INTERVAL ) * 96 #define ZSTIMEINTERVAL_1 96 #define ZSTIMEINTERVAL_2 48 #define ZSTIMEINTERVAL_2T 36 #define ZSTIMEINTERVAL_4 24 #define ZSTIMEINTERVAL_4T 18 #define ZSTIMEINTERVAL_8 12 #define ZSTIMEINTERVAL_8T 9 #define ZSTIMEINTERVAL_16 6 #define ZSTIMEINTERVAL_16T 4.5 #define ZSTIMEINTERVAL_32 3 #define ZSTIMEINTERVAL_32T 1.5 @interface ZSMidiClock : NSObject { PYMIDIManager *pyManager; PYMIDIEndpoint *port; NSMutableArray *notifiers; BOOL isMaster; BOOL signal; unsigned int signalCounter; unsigned int clock; // counts from 1 to 96; // %1 = 96. // %3 = 32. // %6 = 16. // %12 = 8. // %24 = 4. // %48 = 2. unsigned int timeStamp; // AudioGetCurrentHostTime() or from receivedClock float tempo; // for master-mode unsigned int barFactor; // tempo/60*2.4 unsigned int bar; // number of bars unsigned int currentBar; // current bar related to denominator unsigned int numerator, denominator; // eg: 4/4 BOOL debugMode; } @interface ZSMidiScheduler : NSObject { ZSMidiClock *mc; unsigned int timeIntervals[ZSTIMEINTERVAL_MAX]; // eg ZSTIMEINTERVAL_1 ... NSMutableDictionary *knownDestinations; struct ZSMidiMessageQueue queue[ZSQUEUE_MAX]; // see ZSMidiBasics for struct definition! // 32 * 3 (32.note) = 96 // the queue schedules events by bars, // NOT timecode :) BOOL debugMode; } #pragma mark SCHEDULERS -(void)scheduleMsg:(Byte*)msg quantizedFor:(int)interval // see ZSTIMEINTERVAL withLength:(int)length forDestination:(id)destinationObject { // if no clock is available, messages should be sent right away // ... // new message struct ZSMidiMessage scheduledMsg; unsigned int timeUnit = interval; // send NoteOn asap, noteOff after 'length' if(isNOTE_ON(msg[0])) { // schedule corresponding NOTE_OFF if(length > 0) { unsigned int noteOff = ((int) MIDI_NOTE_OFF) + channelOfMessage((int) msg[0]) - 1; Byte noteOffMsg[] = { noteOff, msg[1], msg[2] }; unsigned int newInterval = interval + length; if(newInterval > 96) { newInterval = newInterval % 96; } [self scheduleMsg:noteOffMsg quantizedFor:newInterval withLength:0 forDestination:destinationObject]; } } //scheduledMsg.timeStamp = [mc timeStamp] + ( 1 / length * 96 ); scheduledMsg.message[0] = (unsigned int) msg[0]; scheduledMsg.message[1] = (unsigned int) msg[1]; scheduledMsg.message[2] = (unsigned int) msg[2]; scheduledMsg.destinationID = uniqueZSID; // add to queue unsigned int numberOfEntries = queue[timeUnit].numberOfEntries + 1; if(numberOfEntries >= ZSQUEUE_MAX) { NSLog(@"queue is full. resetting queue...\nplease increase ZSQUEUE_MAX"); // reset queue [self resetQueue]; return; } queue[timeUnit].numberOfEntries = numberOfEntries; queue[timeUnit].ZSMidiMessages[numberOfEntries - 1] = scheduledMsg; // debug if(debugMode) { NSLog(@"Msg queued: %i for %i ->Destination: %i", scheduledMsg.message[0], timeUnit, scheduledMsg.destinationID); } } #pragma mark CLOCK RECEIVER // after registering @ mc this is called 96 times / bar -(void)receiveClock { _Bool tester; unsigned int timeUnit; unsigned int n; for(n=0; n<ZSTIMEINTERVAL_MAX; n++) { // eg: all "24" events get fired if clock is "0/24/48/72/96" // => 48 % 24 = 0 => also passt intervall 24 tester = [mc clock] % timeIntervals[n]; if(tester != 0) { continue; } else { timeUnit = timeIntervals[n]; } if(queue[timeUnit].numberOfEntries > 0) { // build packets Byte msg[3]; int destinationID; id destinationObject; NSString *key; unsigned int i, j; for(i=0; i<queue[timeUnit].numberOfEntries; i++) { // prepare outPacket MIDIPacketList outPacketList; MIDIPacket *outPacket = MIDIPacketListInit(&outPacketList); for(j=0; j<3; j++) { msg[j] = queue[timeUnit].ZSMidiMessages[i].message[j]; } destinationID = queue[timeUnit].ZSMidiMessages[i].destinationID; key = [[NSNumber numberWithInt:destinationID] stringValue]; destinationObject = [knownDestinations valueForKey:key]; if(destinationObject == nil) { // reset queue [self resetQueue]; return; } else { unsigned int msgLength = lengthOfMessage(msg[0]); unsigned int timeStamp = [mc timeStamp]; outPacket = MIDIPacketListAdd(&outPacketList, sizeof(outPacketList), outPacket, timeStamp, msgLength, msg); // send packetList [destinationObject performSelector:@selector(processScheduledMIDIPacketList:) withObject:[NSData dataWithBytes:&outPacketList length:sizeof(outPacketList)]]; } // debug if(debugMode) { NSLog(@"Msg sent: %i @ %i", msg[0], timeUnit); } } // remove sent messages from queue queue[timeUnit].numberOfEntries = 0; } // loop next possible interval... } } #pragma mark - #pragma mark ACTIONS // action methods // message receiver -(void)processMIDIPacketList:(const MIDIPacketList*)inPacketList sender:(id)sender { if(! isMaster) { // receive clock 0xF8 const MIDIPacket *inPacket; unsigned int i, j; // loop packets for(i=0; i<inPacketList->numPackets; i++) { inPacket = &inPacketList->packet[i]; timeStamp = (unsigned int) inPacket->timeStamp; // loop messages for(j=0; j<inPacket->length; j+=2) { switch(inPacket->data[j]) { case MIDI_CLOCK: clock++; if(clock > 96) { clock = 1; } signalCounter++; if(signalCounter >= 24) { signal = YES; signalCounter = 0; // update bar bar ++; } case MIDI_START: clock = 0; bar = 1; break; case MIDI_CONTINUE: clock = 0; bar = 1; case MIDI_STOP: clock = 0; bar = 1; break; } } } } } Michael :) Quote Link to comment Share on other sites More sharing options...
audiocommander Posted November 4, 2006 Report Share Posted November 4, 2006 ...forgot this:#pragma mark structDeclarations struct ZSMidiMessage { unsigned int message[3]; unsigned int destinationID; }; struct ZSMidiMessageQueue { struct ZSMidiMessage ZSMidiMessages[96]; unsigned int numberOfEntries; }; I think the code above is still too long to read; I picked up the most relevant lines in the code again: unsigned int timeIntervals[ZSTIMEINTERVAL_MAX]; // eg ZSTIMEINTERVAL_1 ... struct ZSMidiMessageQueue queue[ZSQUEUE_MAX]; -(void)scheduleMsg:(Byte*)msg { // schedule corresponding NOTE_OFF if(length > 0) { unsigned int noteOff = ((int) MIDI_NOTE_OFF) + channelOfMessage((int) msg[0]) - 1; Byte noteOffMsg[] = { noteOff, msg[1], msg[2] }; unsigned int newInterval = interval + length; if(newInterval > 96) { newInterval = newInterval % 96; } [self scheduleMsg:noteOffMsg quantizedFor:newInterval withLength:0 forDestination:destinationObject]; } } // add to queue unsigned int numberOfEntries = queue[timeUnit].numberOfEntries + 1; queue[timeUnit].numberOfEntries = numberOfEntries; queue[timeUnit].ZSMidiMessages[numberOfEntries - 1] = scheduledMsg; } #pragma mark CLOCK RECEIVER // after registering @ mc this is called 96 times / bar -(void)receiveClock { for(n=0; n<ZSTIMEINTERVAL_MAX; n++) { // eg: all "24" events get fired if clock is "0/24/48/72/96" // => 48 % 24 = 0 => also passt intervall 24 tester = [mc clock] % timeIntervals[n]; if(tester != 0) { continue; } else { timeUnit = timeIntervals[n]; } if(queue[timeUnit].numberOfEntries > 0) { // send packetList [destinationObject performSelector:@selector(processScheduledMIDIPacketList:) withObject:[NSData dataWithBytes:&outPacketList length:sizeof(outPacketList)]]; } // remove sent messages from queue queue[timeUnit].numberOfEntries = 0; } // loop next possible interval... } } -(void)processMIDIPacketList:(const MIDIPacketList*)inPacketList sender:(id)sender { if(! isMaster) { // receive clock 0xF8 // loop packets switch(inPacket->data[j]) { case MIDI_CLOCK: clock++; if(clock > 96) { clock = 1; } signalCounter++; if(signalCounter >= 24) { signal = YES; signalCounter = 0; // update bar bar ++; } } } I may add, that NOTE-ON signals already come synced; but you see the concept how the corresponding NOTE-OFFs are scheduled. I just have to call [self scheduleMsg:noteOffMsg quantizedFor:newInterval withLength:0]Michael Quote Link to comment Share on other sites More sharing options...
stryd_one Posted November 4, 2006 Report Share Posted November 4, 2006 ssshhhhhhaaaaaaaaaAAAABOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOMMMMMMMMMM!!!!!!!!!!!!!!Watch out for pieces of flying brains!edit: For those not in my timezone, the reason for the explosion:« Reply #13 on: Today at 05:17:16 »Between this and seqv3, I'm starting to think complex code should carry like a Surgeon General's Health Warning ... 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.