electro-music.com   Dedicated to experimental electro-acoustic
and electronic music
 
    Front Page  |  Radio
 |  Media  |  Forum  |  Wiki  |  Links
Forum with support of Syndicator RSS
 FAQFAQ   CalendarCalendar   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   LinksLinks
 RegisterRegister   ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in  Chat RoomChat Room 
go to the radio page Live at electro-music.com radio 1 Please visit the chat
poster
 Forum index » DIY Hardware and Software » ChucK programming language
MIDI Canon
Post new topic   Reply to topic Moderators: Kassen
Page 1 of 1 [4 Posts]
View unread posts
View new posts in the last week
Mark the topic unread :: View previous topic :: View next topic
Author Message
Antimon



Joined: Jan 18, 2005
Posts: 4145
Location: Sweden
Audio files: 371
G2 patch files: 100

PostPosted: Sat May 11, 2013 4:53 pm    Post subject: MIDI Canon Reply with quote  Mark this post and the followings unread

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
Back to top
View user's profile Send private message Visit poster's website
Antimon



Joined: Jan 18, 2005
Posts: 4145
Location: Sweden
Audio files: 371
G2 patch files: 100

PostPosted: Sat May 11, 2013 4:54 pm    Post subject: Reply with quote  Mark this post and the followings unread

Here's a file with the source code.


canon.ck
 Description:

Download
 Filename:  canon.ck
 Filesize:  4.11 KB
 Downloaded:  569 Time(s)


_________________
Antimon's Window
@soundcloud @Flattr home - you can't explain music
Back to top
View user's profile Send private message Visit poster's website
MusicMan11712



Joined: Aug 08, 2009
Posts: 1082
Location: Out scouting . . .

PostPosted: Sat May 11, 2013 5:09 pm    Post subject: Reply with quote  Mark this post and the followings unread

Thanks for posting the code and the explanation. I haven't done anything with ChucK in a while, but this might inspire me. Glad I was around to hear the live demo.
Back to top
View user's profile Send private message
Antimon



Joined: Jan 18, 2005
Posts: 4145
Location: Sweden
Audio files: 371
G2 patch files: 100

PostPosted: Sun May 12, 2013 4:05 am    Post subject: Reply with quote  Mark this post and the followings unread

Here's a recording (warts and all) of part of my test run of this that I streamed yesterday, thanks for listening!

This was set up with three delays:

- one direct to piano
- one delayed 1 second, shifted 7 semitones to xylophone
- one delayed 2 seconds, shifted 5 semitones to glockenspiel

The sounds are made with Pianoteq. Since this is midi, I played and recorded all of this as midi data one three tracks in Live, so I can tweak it around - remove, add or move notes - if I want.


triple canon.mp3
 Description:

Download
 Filename:  triple canon.mp3
 Filesize:  2.44 MB
 Downloaded:  1131 Time(s)


_________________
Antimon's Window
@soundcloud @Flattr home - you can't explain music
Back to top
View user's profile Send private message Visit poster's website
Display posts from previous:   
Post new topic   Reply to topic Moderators: Kassen
Page 1 of 1 [4 Posts]
View unread posts
View new posts in the last week
Mark the topic unread :: View previous topic :: View next topic
 Forum index » DIY Hardware and Software » ChucK programming language
Jump to:  

You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Forum with support of Syndicator RSS
Powered by phpBB © 2001, 2005 phpBB Group
Copyright © 2003 through 2009 by electro-music.com - Conditions Of Use