/*------------------------------------------------------------------------------ NAME Sweetener DESCRIPTION Optimises MIDI harmonies by retuning on-the-fly. COPYRIGHT Dave Keenan, Smalltalk Computing, 10-May-95. ------------------------------------------------------------------------------*/ #include #include #include #include #include #include #include #include #include #include #include SCCSet(char reg, char value) { /* Sets an SCC register with a value */ Ptr ctrlWrite; ctrlWrite = LMGetSCCWr() + 2; /* +2 for modem port +0 for printer port */ *ctrlWrite = reg; /* set SCC register */ *ctrlWrite = value; /* put the value into the reg */ reg = reg; /* add some delay... */ reg = reg; } #define WR4 0x04 /* SCC Reg 4 */ #define STOP1 0x04 #define X1CLOCK 0x00 #define X16CLOCK 0x40 #define X32CLOCK 0x80 #define X64CLOCK 0xC0 #define WR11 0x0B /* SCC Reg 11 */ #define TCLOCKTR 0x08 #define TCLOCKBR 0x10 #define RCLOCKTR 0x20 #define RCLOCKBR 0x40 short inRefNum; short outRefNum; char inBuf[1024]; OpenModemPortForMidi() { SerShk shakeFlags; OSErr errorCode; /* Ensure the serial drivers are in a known state, in case we screwed them up last time. Too bad if someone else was using them! */ KillIO(ainRefNum); CloseDriver(ainRefNum); KillIO(aoutRefNum); CloseDriver(aoutRefNum); /* Open the Modem Port serial drivers */ errorCode = opendriver(".AOut", &outRefNum); if (errorCode != 0) { SysBeep(1); } errorCode = opendriver(".AIn", &inRefNum); if (errorCode != 0) { SysBeep(1); } /* A bit-rate parameter value of 57 gives the closest approximation to 31.25kb/s when the SCC is set for x1 clock (usu. x16) and the Mac's internal 3.672MHz clock is used. 3.672M/(2x(57+2)) = 31.12k, only 0.5% slower. But this is irrelevant if we choose to use an external clock. */ SerReset(outRefNum, 57+data8+noParity+stop10); /* Set up for no handshaking */ shakeFlags.fXOn = 0; /* XOn flow control enabled flag */ shakeFlags.fCTS = 0; /* CTS flow control enabled flag */ shakeFlags.xOn = 0; /* XOn character */ shakeFlags.xOff = 0; /* XOff character */ shakeFlags.errs = 0; /* errors mask bits */ shakeFlags.evts = 0; /* event enable mask bits */ shakeFlags.fInX = 0; /* Input flow control enabled flag */ shakeFlags.fDTR = 0; /* DTR input flow control flag */ SerHShake(outRefNum, &shakeFlags); /* Use the external clock supplied by the MIDI interface. The SCC's baud rate generator is not used in this case. */ SCCSet(WR11,(TCLOCKTR | RCLOCKTR)); /* We have a choice of x1, x16, x32, x64 clock. 1MHz = 31.25k x 32 */ SCCSet(WR4,(STOP1 | X32CLOCK)); /* Since there is no flow control, ensure we have a big enough input buffer. The default one is only 64 bytes. */ SerSetBuf(inRefNum, inBuf, 1024); } CloseModemPortForMidi() { SCCSet(WR11,(TCLOCKBR | RCLOCKBR)); SCCSet(WR4,(STOP1 | X16CLOCK)); CloseDriver(inRefNum); CloseDriver(outRefNum); } SendMidiByte(unsigned char aByte) { long count; count = 1; FSWrite(outRefNum, &count, &aByte); /* printf("%2x", aByte); printf(" "); fflush(stdout); */ } OtherSendMidiBytes(void *bytes, long count) { FSWrite(outRefNum, &count, bytes); } SendMidiBytes(unsigned char *bytes, long count) /* not used */ { for (; count > 0; count--) { SendMidiByte(*bytes++); } } GetRawMidiByte(unsigned char *aByte) { long count; do { /* Busy waiting */ SerGetBuf(inRefNum, &count); } while (count == 0); count = 1; FSRead(inRefNum, &count, aByte); } GetMidiByte(unsigned char *aByte) { /* Passes real-time messages (bytes F8..FF) straight thru. They may otherwise appear in the middle of other messages and complicate their parsing */ GetRawMidiByte(aByte); while ((*aByte & 0xF8) == 0xF8) { SendMidiByte(*aByte); GetRawMidiByte(aByte); } } Boolean GetRawMidiByteWithTimeout(unsigned char *aByte, long endTicks) { long count; do { /* Busy waiting */ SerGetBuf(inRefNum, &count); } while ((count == 0) && (endTicks - TickCount() > 0)); if (count == 0) return false; count = 1; FSRead(inRefNum, &count, aByte); return true; } Boolean GetMidiByteWithTimeout(unsigned char *aByte, long endTicks) { /* Passes real-time messages (bytes F8..FF) straight thru. They may otherwise appear in the middle of other messages and complicate their parsing */ do { if (!GetRawMidiByteWithTimeout(aByte, endTicks)) return false; if ((*aByte & 0xF8) != 0xF8) break; SendMidiByte(*aByte); } while(1); return true; } /* Max notes per chord. */ #define kMaxChordSize 16 /* Constants for first index of chord array */ #define kNote 0 #define kVeloc 1 /* These global variables contain the state of the adaptive tuner. Actually the whole state is in the seven scaleFifths. The others variables hold the values of several useful functions of this state. */ short scaleFifths[7]; /* Initially F C G D A E B */ short minFifth; /* The minimum fifth of the scale notes. */ short maxFifth; /* The minimum fifth of the scale notes. */ short twiceMid; /* minFifth + maxFifth, i.e. twice the mean */ short midFifth; /* The rounded mean of minFifth and maxFifth. */ /* The midi tuning-table message */ struct { unsigned char startSysEx; /* F0 Midi system-exclusive message header */ unsigned char manufID; /* 41 Roland */ unsigned char deviceID; /* 00 Factory setting */ unsigned char modelID; /* 42 GS standard */ unsigned char commandID; /* 12 Set data */ unsigned char address[3]; unsigned char centsOffset[12]; /* the tuning table (offset binary, $40 = 0) */ unsigned char checksum; /* The 7 lsbs of the two-complement of the sum of the */ /* address and data bytes */ unsigned char endSysEx; /* F7 Midi end-of-system-exclusive message terminator */ } tuningMsg; short Div(short dividend, short divisor) { /* Clock division */ if (divisor < 0) { if (dividend <= 0) { return dividend/divisor ; } else { return (dividend-1)/divisor - 1; } } else { if (dividend < 0) { return (dividend+1)/divisor - 1; } else { return dividend/divisor; } } } short Mod(short dividend, short divisor) { /* Clock remainder */ short result; result = dividend % divisor; if (divisor < 0) { if (result > 0) { result += divisor; } } else { if (result < 0) { result += divisor; } } return result; } short NoteToPos(short note) { /* Give the note's position, -5..6, relative to midFifth */ return (note*7 + 15 - midFifth)%12 - 5; } short PosToNote(short pos) /* Given relative fifth -5..6, return midi note 0..11 */ { return (pos + 14 + midFifth)*7 %12; } short NoteToFifth(short note) { /* Give the note's absolute position, -14..16 in the cycle of fifths, D = 0 */ return NoteToPos(note) + midFifth; } short FifthToNaturalFifth(short fifth) { /* Give the fifth-number for the natural of this fifth. F..B = -3..3 */ return (fifth + 17)%7 - 3; } ComputeChecksum() { short sum; unsigned char *addr; unsigned char *endAddr; sum = 0; addr = &tuningMsg.address[0]; endAddr = &tuningMsg.checksum; for (; addr= 'a') { ch -= 32; } /* Convert to upper case */ result = ((ch - 63)*2 %7 + 11)*7; i = 1; ch = name[i]; while ((ch != 0) && (i<4)) { switch (ch) { case 'b': result = result+11; break; case 'B': result = result+11; break; case '3': result = result+1; break; case '#': result = result+1; break; case 'x': result = result+2; break; case 'X': result = result+2; break; } i++; ch = name[i]; } return result%12; } else { return -1; } } PrintFifth(short fifth) { switch (fifth) { case -14: printf("Dbb"); break; case -13: printf("Abb"); break; case -12: printf("Ebb"); break; case -11: printf("Bbb"); break; case -10: printf("Fb" ); break; case -9: printf("Cb" ); break; case -8: printf("Gb" ); break; case -7: printf("Db" ); break; case -6: printf("Ab" ); break; case -5: printf("Eb" ); break; case -4: printf("Bb" ); break; case -3: printf("F" ); break; case -2: printf("C" ); break; case -1: printf("G" ); break; case 0: printf("D" ); break; case 1: printf("A" ); break; case 2: printf("E" ); break; case 3: printf("B" ); break; case 4: printf("F#" ); break; case 5: printf("C#" ); break; case 6: printf("G#" ); break; case 7: printf("D#" ); break; case 8: printf("A#" ); break; case 9: printf("E#" ); break; case 10: printf("B#" ); break; case 11: printf("Fx" ); break; case 12: printf("Cx" ); break; case 13: printf("Gx" ); break; case 14: printf("Dx" ); break; case 15: printf("Ax" ); break; case 16: printf("Ex" ); break; } } PrintNote(short note) { PrintFifth(NoteToFifth(note)); } PrintRange() { PrintFifth(midFifth - 5); printf("("); PrintFifth(minFifth); printf(".."); PrintFifth(maxFifth); printf(")"); PrintFifth(midFifth + 6); } PrintChord(char chord[][2], short chordSize) { short i; Boolean anyPrinted; anyPrinted = false; for (i=0; i maxFifth) { maxFifth = scaleFifths[i]; } } } short Odd(short i) { return i & 1; } SendTuningTable() { /* send tuning table (system exclusive) messages for all 16 voices */ /* short i; */ /* for (i=0; i<16; i++) { tuningMsg.address[1] = i+16; */ ComputeChecksum(); SendMidiBytes((unsigned char *)&tuningMsg, sizeof(tuningMsg)); /* } */ PrintRange(); printf("\n"); fflush(stdout); } ShiftKeys(short delta) { short i; if (delta < 0) { for (i=0; i<-delta; i++) { tuningMsg.centsOffset[PosToNote(i-5)] += 39; } } else { for (i=0; i oldTwiceMid) { midFifth = twiceMid / 2 - 1; } else if (twiceMid == oldTwiceMid) { offset = fifth - twiceMid / 2; if ((offset == -2) || (offset == -3)) { midFifth = twiceMid / 2 - 1; } else if ((offset == 2) || (offset == 3)) { midFifth = twiceMid / 2; } /* else don't change midFifth */ } } if (midFifth < -9) { midFifth += 12; ShiftScaleUp12(); } if (midFifth > 10) { midFifth -= 12; ShiftScaleDown12(); } delta = midFifth - oldMidFifth; if (delta != 0) { ShiftKeys(delta); SendTuningTable(); } } SendChord(char chordStartByte, char chord[][2], short chordSize) { short i; SendMidiByte(chordStartByte); for (i=0; i=keyShiftMin) && (k<= keyShiftMax); k = (k<=0?1-k:-k)) { /* k = 0, 1, -1, 2, -2 */ min = 99; max = -99; j = 0; midFifth = midFifth + k; for (i=0; i fifth) { min = fifth;} if (max < fifth) { max = fifth;} } } chordFifthsSize = j; range = max - min; if ((minRange > range) && NoOverloads(chordFifths, chordFifthsSize)) { minRange = range; bestKeyShift = k; } } midFifth = oldMidFifth + bestKeyShift; if (bestKeyShift == 0) { SendChord(chordStartByte, chord, chordSize); } else { /* Send immediately any notes that wont change. */ /* Nothing for now */ /* Send tuning table */ ShiftKeys(bestKeyShift); SendTuningTable(); /* Send remaining notes */ SendChord(chordStartByte, chord, chordSize); /* For now */ } UpdateScaleFromChord(chordFifths, chordFifthsSize); } /* Bug: We're losing some note-offs on some chords */ Sweetener() { unsigned char midiByte, note, velocity; char chordStartByte; short chordSize; char chord[kMaxChordSize][2]; InitTuningTable(); SendTuningTable(); /* skip any non-status bytes */ do { GetMidiByte(&midiByte); if ((midiByte & 0x80) != 0) break; SendMidiByte(midiByte); } while (true); /* Parse the midi stream to find note-on messages and pass them as chords to the adaptive tuning routine. */ note = 127; while (note > 28) { /* bottom E or lower gets us out */ switch (midiByte & 0xF0) { case 0x90: /* Note-on */ chordStartByte = midiByte; chordSize = 0; do { if (!GetMidiByteWithTimeout(&midiByte, TickCount()+2)) { AdaptForChord(chordStartByte, chord, chordSize); GetMidiByte(&midiByte); break; } if ((midiByte & 0x80) != 0) { AdaptForChord(chordStartByte, chord, chordSize); break; } note = midiByte; GetMidiByte(&midiByte); velocity = midiByte; if (chordSize < kMaxChordSize) { chord[chordSize][kNote] = note; chord[chordSize++][kVeloc] = velocity; } } while (true); break; default: do { SendMidiByte(midiByte); GetMidiByte(&midiByte); } while ((midiByte & 0x80) == 0); break; } } } Test() { short note, i, j; for (i=-4; i<5; i++) { for (j=-4; j<5; j++) { printf("%d", i?Div(j,i):0); } printf("\n"); } for (i=-4; i<5; i++) { for (j=-4; j<5; j++) { printf("%d", i?Mod(j,i):0); } printf("\n"); } InitTuningTable(); note = ReadNote(); while (note != -1) { PrintNote(note); printf("\n"); AdaptForFifth(NoteToFifth(note)); note = ReadNote(); } } PrintMidiBytes() { unsigned char aByte; short i; i = 0; aByte = 127; while (aByte != 28) { /* bottom E or lower gets us out */ /* Low E is 28, pitch bend is 0xE0 */ GetRawMidiByte(&aByte); SendMidiByte(aByte); /* all midi data passed thru immediately */ printf("%2x", aByte); i = (i + 1) % 24; if (i == 0) { printf("\n"); fflush(stdout); } else { printf(" "); fflush(stdout); } } } main() { do { OpenModemPortForMidi(); /* On the Yamaha YFP-70, to disconnect the keyboard from the synthesizer hold the MIDI/TRANSPOSE button while pressing the E. PIANO button */ printf("Started. Hit bottom E to stop.\n"); fflush(stdout); /* InitTuningTable(); SendTuningTable(); PrintMidiBytes(); */ Sweetener(); CloseModemPortForMidi(); printf("\nStopped. Hit return to start again, or Command-Q to quit."); fflush(stdout); gets(inBuf); } while (true); } /* These timeouts should be implemented some time if (LongTimeout()) { InitTuningTable(); SendTuningTables(); } else { if (ShortTimeout()) { if (midFifth < -7) { ShiftKeys(12); ShiftScaleUp12(); SendTuningTables(); } if (midFifth > 7) { ShiftKeys(-12); ShiftScaleDown12(); SendTuningTables(); } } } */