Jump to content

Quantize box


dcer10
 Share

Recommended Posts

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

Link to comment
Share on other sites

...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 :D

Michael

Link to comment
Share on other sites

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

Link to comment
Share on other sites

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...

Link to comment
Share on other sites

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!  :P

    then 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
Link to comment
Share on other sites

LOL ;D

Maybe 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 arrays

okay, I think, I need to go to bed, before I'm confusing myself ;D

Cheers!

Michael

(see you at #midibox @efnet?, 've been there today :) )

Link to comment
Share on other sites

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 :)

Link to comment
Share on other sites

...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

Link to comment
Share on other sites

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 ...

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.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  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.

Loading...
 Share

×
×
  • Create New...