Antimon
Joined: Jan 18, 2005 Posts: 4145 Location: Sweden
Audio files: 371
G2 patch files: 100
|
Posted: Sat May 11, 2013 4:53 pm Post subject:
MIDI Canon |
 |
|
Ever since I listened to a talk by ark at an electro-music event (it was broadcast on the stream, not archived to my knowledge) about generating extra melody lines using midi and programming, I have wanted to try this out in ChucK. Here, finally, is a simple attempt I made this evening.
If you want to use it, the crucial stuff is at the top. You create an instance of MidiCanon, and call the input method with the midi device index (as indicated by chuck --probe) and channel you want to listen to. Then for each midi "echo" you want, you call midiCanon.output() with device index, midi channel, time offset and half tone offset. You have to include immediate echo (i.e. direct monitoring of your playing) as an output with time offset = 0::seconds here - if you want it.
When a note is played on the input channel, the program will send that note to all outputs, delayed by the offset that you set up. So if you set an output up with timeOffset = 1::second, then a note will be sent to that output 1 second after it was played on the input.
Also, notes can be shifted using the halfToneOffset. E.g., a halfToneOffset of 7 means that notes on this output will differ from what was played by a fifth. Notes that are shifted this way will be confined to the scale that is also set up near the top of the source code. If halfToneOffset is set to zero, the program will play exactly the note that came on the input, i.e. it will not be confined to the scale.
The code here is set up with three outputs: one direct monitoring, one that is delayed by a second and shifted 7 semitones, and one that is delayed by two seconds and shifted five semitones relative to what is played into the input. Feel free to remove or add inputs.
You can change the scale if you want - the code here is set to C major, defined by scaleBaseTone = 0 (which is C), and seven offsets corresponding to the whole tone intervals of the major scale. Feel free to change these values, and remove or add notes to the scale, keeping the scale values in ascending order and below 12.
Things you can try are different instruments on different outputs, setting different halfToneValues but keeping timeOffset at zero seconds (for chord generation). There may be loads of things you can use this for. I cannot guarantee that there won't be bum notes though, especially if you throw in loads of halfToneOffsets. :)
Code: |
//===== Canon setup =====
MidiCanon midiCanon;
midiCanon.input(3, 0);
midiCanon.output(0, 0, 0::ms, 0);
midiCanon.output(0, 1, 1000::ms, 7);
midiCanon.output(0, 2, 2000::ms, 5);
// C Major - change this if you want some other scale
0 => int scaleBaseTone; // C
[0,2,4,5,7,9,11] @=> int scaleOffsets[]; // Major scale
//===== Code starts ======
fun int restrictNoteToScale(int note) {
int i;
scaleBaseTone => int octaveBase;
while (octaveBase < note) {
12 +=> octaveBase;
}
for (0 => i; i < scaleOffsets.size(); i++) {
if (octaveBase + scaleOffsets[i] <= note) {
return octaveBase + scaleOffsets[i];
}
}
return note; // shouldn't happen, but let's return something useful anyway
}
class NoteEvent {
time triggerTime;
int note;
int velocity;
int state;
}
class NoteEventQueue {
NoteEvent events[1024];
0 => int readIndex;
0 => int writeIndex;
Event noteAdded;
fun NoteEvent pullEvent() {
if (readIndex != writeIndex) {
++readIndex;
if (readIndex >= events.size()) {
0 => readIndex;
}
events[readIndex] @=> NoteEvent@ upcoming;
// <<< "Checking trigger time: ", upcoming.triggerTime/1::second, now/1::second >>>;
if (upcoming.triggerTime > now) {
upcoming.triggerTime => now;
}
return upcoming;
} else {
noteAdded => now;
return pullEvent();
}
}
fun void addEvent(time triggerTime, int note, int velocity, int state) {
if (++writeIndex >= events.size()) {
0 => writeIndex;
}
triggerTime => events[writeIndex].triggerTime;
note => events[writeIndex].note;
velocity => events[writeIndex].velocity;
state => events[writeIndex].state;
noteAdded.signal();
}
}
class MidiCanonOutput {
MidiOut midiOut;
int midiChannel;
dur outOffset;
int halfToneOffset;
NoteEventQueue queue;
fun void addEvent(int note, int velocity, int state) {
queue.addEvent(now + outOffset, note, velocity, state);
}
fun void queuePuller() {
MidiMsg midiMsg;
int note;
while (true) {
queue.pullEvent() @=> NoteEvent event;
event.note => note;
if (halfToneOffset != 0) {
restrictNoteToScale(note + halfToneOffset) => note;
}
// <<< "Send note :", midiChannel, event.note, event.velocity >>>;
0x90 + midiChannel => midiMsg.data1;
note => midiMsg.data2;
event.velocity => midiMsg.data3;
midiOut.send(midiMsg);
}
}
spork ~ queuePuller();
}
class MidiCanon {
MidiIn midiIn;
int midiInChannel;
MidiCanonOutput outputs[0];
fun void input(int deviceNumber, int midiChannel) {
midiChannel => midiInChannel;
if (midiIn.open(deviceNumber)) {
<<< "Opened device for input: ", midiIn.name() >>>;
} else {
<<< "Couldn't open device for input #", deviceNumber >>>;
}
spork ~ listener();
}
fun void output(int deviceNumber, int midiChannel, dur offset, int halfToneOffset) {
MidiCanonOutput outputStruct;
midiChannel => outputStruct.midiChannel;
offset => outputStruct.outOffset;
halfToneOffset => outputStruct.halfToneOffset;
if (outputStruct.midiOut.open(deviceNumber)) {
<<< "Opened device for output: ", outputStruct.midiOut.name() >>>;
} else {
<<< "Couldn't open device for output #", deviceNumber >>>;
}
outputs << outputStruct;
}
fun void controlMessage(int channel, int control, int value) {
//<<< "MIDI #", channel , "CC", control, "=", value >>>;
}
fun void noteMessage(int note, int velocity, int state) {
// <<< "note msg ", note, velocity, state >>>;
for (0 => int i; i < outputs.size(); i++) {
outputs[i].addEvent(note, velocity, state);
}
}
fun void listener() {
MidiMsg midiMsg;
while (true) {
midiIn => now;
while (midiIn.recv(midiMsg)) {
// <<< "Received midi: ", midiMsg.data1, midiMsg.data2, midiMsg.data3 >>>;
(midiMsg.data1 & 0xf0) / 16 => int message;
midiMsg.data1 & 0x0f => int channel;
if (channel == midiInChannel) {
if (message == 0x0b) {
controlMessage(channel, midiMsg.data2, midiMsg.data3);
} else if (message == 0x09) {
midiMsg.data3 => int velocity;
noteMessage(midiMsg.data2, velocity, (velocity > 0));
} else if (message == 0x08) {
noteMessage(midiMsg.data2, 0, 0);
}
}
}
}
}
}
while (true) {
1::day => now;
} |
_________________ Antimon's Window
@soundcloud @Flattr home - you can't explain music |
|