Forum rss-feed

Forum

Developers: Python...quick...clean...efficient...simple

Most Recent

written by: benjamind2008

Just add the custom scales, and use the interval numbers, ie

0,4,7,12

for the custom scale called "CHORD_Major".

Add custom scales for all the chords listed, and remember to always place the 12. Eg. after the 0,4,7 you put ,12...as the custom scale is set up as "0,4,7,12"...

written by: benjamind2008

Thu, 10 May 2012 11:04:46 +0100 BST

I was able to work out how to get my python script to work at intercepting MIDI IN events, particularly NOTE_ON events to get the barebones of my chord detection system up and running.

There is still a fair way to go, but I'm going to implement 3 things first, then will go ahead with the chord detection routine which will be fairly trivial.

1. A list box to select the MIDI port to listen to.
2. A readout of the current chord and tonic
3. A small exit button to quit the program

The chord detection system will rely on a delay between the last NOTE_ON message, with a number pointing to an array index whereby the number will be reset to 1 every xxx milliseconds. The array will contain a list of notes. Each note_on message will convert the note automatically to a C,Db,D,Eb, E,F,Gb, G,Ab, A, Bb, B, and add it to the array at the index, which will increment +1 each NOTE_ON message received, and will be reset when the timer is triggered at the user defined interval. Chords more than 6 notes will simply be ignored, chords less than 2 notes will also be discarded.

I will implement a scroll to enable the user to determine the amount in 50ms increments from 100ms to 2000ms. When the timer is restarted every xxx milliseconds the array index is reset so the chord simply gets re-detected, and if there are no new messages or the messages detected were of the existing chord, the existing chord is used.

Chords will be preset, and won't be user-defined. There will be a README file telling users what they need to do, which will be to add a list of custom scales to the scales list, so EigenD will know what scale to choose when the chord is selected.


written by: 0beron

Thu, 10 May 2012 17:49:24 +0100 BST

I'm always amazed at how productive python is for quick scripts to get things done. Looks like you're well on the way to an implementation :)


written by: benjamind2008

Thu, 10 May 2012 20:31:21 +0100 BST

Even just using a console app with command line parameters works a treat...but I should provide a GUI for the end user to make things easier.

For myself, I'd only need a command-line functionality to take control of the settings.


written by: Bjoern

Fri, 11 May 2012 21:10:06 +0100 BST

I'd be interested in seeing the code - would you be happy to share?


written by: benjamind2008

Sat, 12 May 2012 01:31:07 +0100 BST

When I'm finished which should hopefully be in a couple of weeks.
I've got the heart of the detection code down so now its just a matter of the code specifying the chord and tonic and displaying the chord and talking to Belcanto to set the scale and tonic.
I decided to do a command line based system as I believe it will reduce any possible overheads and make the code more efficient.

Example

Eigenchorder -2 -500 -d
- port number
- detection time
- display chord switch

Port number sets the port number of the MIDI device to listen to.

Detection time sets the amount of time in milliseconds for a chord to be detected, the default is 250ms but some users might want a faster or slower response depending on their system performance.

Display chord switch sets the switch that if turned on (default setting) will display the chord and tonic when a chord has been detected. If switched off it will not display chords.

There will be 3 command line options. Regardless if a port number has been specified or not the program will display a list of MIDI ports with respective port numbers. If a port number is not specified it will automatically select the first available port.

Thats it. It will detect the chord and tonic and basically tell Belcanto to set the appropriate scale and tonic. The user will be required to add a list of custom scales to EigenD


written by: benjamind2008

Sat, 12 May 2012 02:50:42 +0100 BST

It will keep detecting new chords, running off a MIDI port callback function.
The maximum number of notes in a chord will be 7 notes which will make it possible to detect the Major13th chord!

So it will detect pretty much every chord in the musical dictionary ;)

It talks to Belcanto only when a new chord is detected, if it detects C Major twice it will only talk to Belcanto once, this will make Eigenchorder more efficient.

The program can be used with an Alpha or a Tau, but is most appropriate with a Pico due to the Pico's small number of keys.

It will be a console application which will run in the background when the user is running synthesizers and sequencers/audio apps.

CTRL+C when the user is viewing the console will close the program.


written by: benjamind2008

Sat, 12 May 2012 03:00:39 +0100 BST

You will be able to run it with a shortcut so specifying the command line options will be much easier.


written by: benjamind2008

Sat, 12 May 2012 06:58:13 +0100 BST

Will add a new option (-pico18) to make the Pico layout that will allow all 18 playable keys over 2 courses of 9 keys so keys 1-9 and followed by 10-18 will be the layout.

If the -pico18 is not used it will use the standard layout with 4 x 4 courses (16 playable keys)


written by: Bjoern

Sat, 12 May 2012 11:53:39 +0100 BST

Looking forward to it!


written by: benjamind2008

Sat, 19 May 2012 08:47:43 +0100 BST

Almost done. Just need to do the finishing touches and instructions on how to add the custom scales (which are actually just chords basically used as custom scales - are needed for this program to work with EigenD)

All the custom scales start with "CHORD_"

I just need to add the command line features and that's it. Done. When it's finished I'll upload the code to this forum. You'll need python RTMIDI which, for me at least, was a PITA to get working, but should not present any major issues.


written by: Bjoern

Sat, 19 May 2012 10:14:46 +0100 BST

Hi benjamind2008!

Thanks for the update!

I'm on Mac. Are you developing this on Mac or PC? Just asking in terms of getting RTMIDI to work. Would be good if you could post some notes about getting RTMIDI to work for you (esp. if on mac).

Thanks,
Bjoern


written by: benjamind2008

Sat, 19 May 2012 11:54:36 +0100 BST

Well, it's all been finished up. The finishing touches (more elaborate comments, etc) have not been applied just yet, but it appears to work with the script.

Not sure how this works on a Mac, but it shouldn't be difficult if the RTMIDI toolkit has been properly installed. I don't have a Mac, so am not sure what to say. With windows, I installed RTMIDI without any trouble once I had Visual C++ 2008 Express Edition installed. Apparently you need it for RTMIDI when you set it up. Something to do with VCVARSALL.BAT...which before the VC++2008 install it was missing ;)

Here is the code. It's not all that complicated, but it took me a good week to refine it.

import rtmidi_python as rtmidi
import sched, time, unittest
import sys
import optparse
import math
import xmlrpclib

port_to_open = 2
chord_str = ""
midi_num = 0
chord_array = [0, 0, 0, 0, 0, 0 ,0, 0]
mdelay = 0.01 # 10ms

chord_str = ""

# use callback instead
def midi_callback(message, time_stamp):
global chord_str
global chord_array
global midi_num
global port_to_open
# print "MSG: ", message[0], message[1], message[2], midi_num
if message[0] > 143:
if message[0] 7: # number of NOTE_ON messages
midi_num = 7

# main program params
prog_condition = True
old_time_count = 0 # main initial time counter
time_count = 0 # main time counter

#command line data structure
intportval = 0
floattiming = 0.250
boolchordprint = True

# time params
midi_port_count = 0 #define the port count integer

#midi param specifics
midi_num_events = 0 # total events in a xxx-second cycle
#important for determining the chord size. 3 events = 3 notes, etc.
midi_data_whole = 0
midi_data_1 = 0
midi_data_2 = 0

midi_in = None
midi_in = rtmidi.MidiIn()
for port_name in midi_in.ports:
print port_name, midi_port_count
midi_port_count = midi_port_count + 1

old_time_count = time.time()

# main program
stored_time = time.time() #we set the stored time to the current time

#start up the server
belscript=xmlrpclib.Server('http://localhost:55553')

# let's determine what options the user has specified in the command line
print 'ARGV :', sys.argv[1:]
parser = optparse.OptionParser()
parser.add_option('-p', '--p',
dest="portval",
default=0,
)
parser.add_option('-c', '--c',
dest="chordprintval",
default=False,
action="store_true",
)
parser.add_option('-t', '--t',
dest="timingval",
default=0.25,
type="float",
)
options, remainder = parser.parse_args()
print 'PORT NUMBER : ', options.portval
print 'PRINTING OFF : ', options.chordprintval
print 'TIMING SEC : ', options.timingval
intportval = int(options.portval)
floattiming = options.timingval
boolchordprint = options.chordprintval


# open MIDI port
print ("opening MIDI port")
midi_num = 0
note_index = 0
midi_in.callback = midi_callback
midi_in.open_port(intportval) # we open the port now
stored_time = time.time()
chord_tonic = ""
chord_name = ""
chord_sdat = ""
tonic_belcanto = ""

# tell belcanto to ensure all agents will respond to commands
belcommand = "all join"
belscript.execBelcanto(belcommand)

while (prog_condition == True):
time_count = time.time() - old_time_count
current_time = time.time()
if (time_count > floattiming): #default to 0.25 seconds
# do the chord check here:
#if midi_num > 0: print ("Chord check")
time_count = time.time()
old_time_count = time.time() #reset timer
# test chord tonic
if chord_array[0] % 12 is 0:
chord_tonic = "C"
tonic_belcanto = "notec"
if chord_array[0] % 12 is 1:
chord_tonic = "C#"
tonic_belcanto = "notecsharp"
if chord_array[0] % 12 is 2:
chord_tonic = "D"
tonic_belcanto = "noted"
if chord_array[0] % 12 is 3:
chord_tonic = "D#"
tonic_belcanto = "notedsharp"
if chord_array[0] % 12 is 4:
chord_tonic = "E"
tonic_belcanto = "notee"
if chord_array[0] % 12 is 5:
chord_tonic = "F"
tonic_belcanto = "notef"
if chord_array[0] % 12 is 6:
chord_tonic = "F#"
tonic_belcanto = "notefsharp"
if chord_array[0] % 12 is 7:
chord_tonic = "G"
tonic_belcanto = "noteg"
if chord_array[0] % 12 is 8:
chord_tonic = "G#"
tonic_belcanto = "notegsharp"
if chord_array[0] % 12 is 9:
chord_tonic = "A"
tonic_belcanto = "notea"
if chord_array[0] % 12 is 10:
chord_tonic = "A#"
tonic_belcanto = "noteasharp"
if chord_array[0] % 12 is 11:
chord_tonic = "B"
tonic_belcanto = "noteb"
# determine chord structure
if midi_num > 0:
while note_index 0: chord_sdat = chord_sdat + "," + str(chord_array[note_index] - chord_array[0])
note_index = note_index + 1
#print "Chord string data: ", chord_sdat, " tonic = ", chord_tonic
# determine what the chord is, talk to Belcanto
if chord_sdat == "0,5,7": chord_name = "CHORD_Sus4"
if chord_sdat == "0,5,7,10": chord_name = "CHORD_7Sus4"
if chord_sdat == "0,5,7,10,16": chord_name = "CHORD_7Sus4(10)"
if chord_sdat == "0,2,7": chord_name = "CHORD_Sus2"
if chord_sdat == "0,4,8": chord_name = "CHORD_Aug"
if chord_sdat == "0,4,8,10": chord_name = "CHORD_7(#5)"
if chord_sdat == "0,4,8,11": chord_name = "CHORD_Maj7(#5)"
if chord_sdat == "0,4,7": chord_name = "CHORD_Major"
if chord_sdat == "0,2,4,7": chord_name = "CHORD_Add2"
if chord_sdat == "0,4,5,7": chord_name = "CHORD_Add4"
if chord_sdat == "0,4,7,9": chord_name = "CHORD_6"
if chord_sdat == "0,4,7,10": chord_name = "CHORD_7"
if chord_sdat == "0,4,7,11": chord_name = "CHORD_Maj7"
if chord_sdat == "0,4,7,14": chord_name = "CHORD_Add9"
if chord_sdat == "0,4,5,7,9": chord_name = "CHORD_6Add4"
if chord_sdat == "0,4,5,7,11": chord_name = "CHORD_Maj7Add4"
if chord_sdat == "0,4,7,9,14": chord_name = "CHORD_6Add9"
if chord_sdat == "0,4,7,10,13": chord_name = "CHORD_7(b9)"
if chord_sdat == "0,4,7,10,14": chord_name = "CHORD_9"
if chord_sdat == "0,4,7,10,15": chord_name = "CHORD_7(#9)"
if chord_sdat == "0,4,7,10,21": chord_name = "CHORD_7(13)"
if chord_sdat == "0,4,7,11,14": chord_name = "CHORD_Maj7(9)"
if chord_sdat == "0,4,7,11,21": chord_name = "CHORD_Maj7(13)"
if chord_sdat == "0,4,7,10,13,15": chord_name = "CHORD_7(b9#9)"
if chord_sdat == "0,4,7,10,14,18": chord_name = "CHORD_7(9#11)"
if chord_sdat == "0,4,7,10,14,21": chord_name = "CHORD_7(913)"
if chord_sdat == "0,4,7,11,14,17": chord_name = "CHORD_Maj7(911)"
if chord_sdat == "0,4,7,10,14,17,21": chord_name = "CHORD_7(91113)"
if chord_sdat == "0,4,6": chord_name = "CHORD_b5"
if chord_sdat == "0,4,6,9": chord_name = "CHORD_6(b5)"
if chord_sdat == "0,4,6,10": chord_name = "CHORD_7(b5)"
if chord_sdat == "0,4,6,11": chord_name = "CHORD_Maj7(b5)"
if chord_sdat == "0,4,6,10,15": chord_name = "CHORD_7(b5#9)"
if chord_sdat == "0,3,8,10": chord_name = "CHORD_m7(#5)"
if chord_sdat == "0,3,7": chord_name = "CHORD_Minor"
if chord_sdat == "0,3,7,9": chord_name = "CHORD_m6"
if chord_sdat == "0,3,7,10": chord_name = "CHORD_m7"
if chord_sdat == "0,3,7,11": chord_name = "CHORD_m(Maj7)"
if chord_sdat == "0,3,5,7,10": chord_name = "CHORD_m7Sus4"
if chord_sdat == "0,3,7,9,14": chord_name = "CHORD_m6Add9"
if chord_sdat == "0,3,7,10,13": chord_name = "CHORD_m7(b9)"
if chord_sdat == "0,3,7,10,14": chord_name = "CHORD_m9"
if chord_sdat == "0,3,7,10,14,17": chord_name = "CHORD_m11"
if chord_sdat == "0,3,7,10,14,17,21": chord_name = "CHORD_m13"
if chord_sdat == "0,3,6": chord_name = "CHORD_Dim"
if chord_sdat == "0,3,6,9": chord_name = "CHORD_Dim7"
if chord_sdat == "0,3,6,10": chord_name = "CHORD_m7(b5)"
if boolchordprint == False: print chord_tonic, chord_name, midi_num, "notes" #if printout is enabled, print the chord info
note_index = 0
midi_num = 0
chord_sdat = ""
# talk to belcanto here
# set up the scale (to play notes of the chord)
if chord_name "":
belcommand = "scale to " + chord_name + " set"
belscript.execBelcanto(belcommand)
belcommand = "tonic to " + tonic_belcanto + " set"
belscript.execBelcanto(belcommand)
# the MIDI message printout
midi_in.close_port() # close the port
midi_in = None
midi_out = None
exit


written by: benjamind2008

Sat, 19 May 2012 11:59:24 +0100 BST

To start it, simply use these command line arguments:

-p n [where n is the port number - choose a port where your sequencer is free to output to, I use a loopback driver LoopBe to set up the virtual ports]

-c [don't use this if you want to see the chords printed on the screen as they're detected]

-t 0.25 [this is an example, and is also the default mind you, 0.25 seconds, the time between the chord detection routines being executed, you can specify lower if you want]


written by: benjamind2008

Sat, 19 May 2012 12:02:57 +0100 BST

Just add the custom scales, and use the interval numbers, ie

0,4,7,12

for the custom scale called "CHORD_Major".

Add custom scales for all the chords listed, and remember to always place the 12. Eg. after the 0,4,7 you put ,12...as the custom scale is set up as "0,4,7,12"...



Please log in to join the discussions