ER-102: Using scales.py to generate your own scales

41 views
Skip to first unread message

Brian@O|D

unread,
Feb 13, 2017, 1:17:06 AM2/13/17
to odevices
A user asked the following question recently:
 
Quick question. I think I want to make a few scales of my own for the ER101/2. What I was thinking of doing was creating a few that are in different keys so that effectively I could easily switch between say Pentatonic in E Minor to say, G Minor by simply changing the voltage table. I know I can do this using a precision adder outside of the ER101/2 but wanted to do as much as possible in the ER101/2.
So, am looking at the python script in the scales.zip file and see the following for Pentatonic Minor.

"Pentatonic Minor: 0, 0, 0, 3, 3, 5, 5, 7, 7, 7, 10, 10"

I think this creates the scale in C. I am guessing that if I add 4 to each number, subtract 12 when necessary and then rerun the script you’d get there. i.e.,

“Pentatonic E Minor: 2, 2, 4, 4, 4, 7, 7, 9, 9, 11, 11, 11”

Does this make sense?

Also, is there some logic to why some numbers are repeated twice, but others three times? 

To which I answered:

Just adding 4 is not quite right because the tables in the script are mappings from 12ET to the desired scale.  See next paragraph for the explanation.  You would start with all 12 notes of the chromatic scale:

0  1  2  3  4  5  6  7  8  9  10  11

and then start remapping them.  You would start by first indicating which notes are in the desired scale:

0 1 (2) 3 (4) 5 6 (7) 8 (9) 10 (11)

Finally its up to you to decide what to do with the out-of-scale notes and how you want them to be mapped.  For example 'round up' is one possibility:

2 2 (2) 4 (4) 7 7 (7) 9 (9) 11 (11)

and you are done.

The reason for the repeats is to keep the 12 entries per octave structure so that if  you have an existing melody written chromatically (12ET) then you can just swap in any of these scales and get a musically sensible result.  In other words, you are mapping each of the 12 chromatic notes to a note in another scale. How you do the mapping is really up to you but it has to have 12 entries for this to work.

Voltage tables in general of course don't need to follow this 12-notes per octave structure. 

Brian@O|D

unread,
Feb 13, 2017, 1:18:02 AM2/13/17
to odevices
Here is scales.py for reference:

# This Python script converts the scales given in text format below
# to the ER-102's voltage table format.


import ctypes


# The following scales were provided by EAT@dnp-music
input_data
= """
Chromatic: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
Major: 0, 0, 2, 2, 4, 5, 5, 7, 7, 9, 9, 11
Minor: 0, 0, 2, 3, 3, 5, 5, 7, 7, 8, 10, 10
Harmonic Minor: 0, 0, 2, 3, 3, 5, 5, 7, 7, 8, 11, 11
Melodic Minor: 0, 0, 2, 3, 3, 5, 5, 7, 7, 9, 11, 11
Pentatonic: 1, 1, 1, 3, 3, 6, 6, 6, 8, 8, 10, 10
Pentatonic Neutral: 0, 0, 2, 2, 2, 5, 5, 7, 7, 7, 10, 10

Pentatonic Minor: 0, 0, 0, 3, 3, 5, 5, 7, 7, 7, 10, 10
Pentatonic Major: 0, 0, 2, 2, 4, 4, 4, 7, 7, 9, 9, 9
Blues: 0, 0, 0, 3, 3, 5, 6, 7, 7, 7, 10, 10
Dorian: 0, 0, 2, 3, 3, 5, 5, 7, 7, 9, 10, 10
Mixolydian: 0, 0, 2, 2, 4, 5, 5, 7, 7, 9, 10, 10
Phrygian: 0, 1, 1, 3, 3, 5, 5, 7, 8, 8, 10, 10
Lydian: 0, 0, 2, 2, 4, 4, 6, 7, 7, 9, 9, 11
Locrian: 0, 1, 1, 3, 3, 5, 6, 6, 8, 8, 10, 10
Dim Half: 0, 1, 1, 3, 4, 4, 6, 7, 7, 9, 10, 10
Dim Whole: 0, 0, 2, 3, 3, 5, 6, 6, 8, 9, 9, 11
Augmented: 0, 0, 0, 3, 4, 4, 6, 6, 8, 8, 8, 11
Roumanian Minor: 0, 0, 2, 3, 3, 3, 6, 7, 7, 9, 10, 10
Spanish Gypsy: 0, 1, 1, 1, 4, 5, 5, 7, 8, 8, 10, 10
Diatonic: 0, 0, 2, 2, 4, 5, 5, 7, 7, 9, 9, 9
Double Harmonic: 0, 1, 1, 1, 4, 5, 5, 7, 8, 8, 8, 10
Eight Tone Spanish: 0, 1, 1, 3, 4, 5, 6, 6, 8, 8, 10, 10
Enigmatic: 0, 1, 1, 1, 4, 4, 6, 6, 8, 8, 10, 11
Algerian: 0, 0, 2, 3, 3, 5, 6, 7, 8, 8, 8, 11
Arabian A: 0, 0, 2, 3, 3, 5, 6, 6, 8, 9, 9, 11
Arabian B: 0, 0, 2, 2, 4, 5, 6, 6, 8, 8, 10, 10
Balinese: 0, 1, 1, 3, 3, 3, 7, 8, 8, 8, 8, 8
Byzantine: 0, 1, 1, 1, 4, 5, 5, 7, 8, 8, 8, 11
Chinese: 0, 0, 0, 0, 4, 4, 6, 7, 7, 7, 7, 11
Egyptian: 0, 0, 2, 2, 2, 5, 5, 7, 7, 7, 10, 10
Hindu: 0, 0, 2, 2, 4, 5, 5, 7, 8, 8, 10, 10
Hirajoshi: 0, 0, 2, 3, 3, 3, 3, 7, 8, 8, 8, 8
Hungarian Gypsy: 0, 0, 2, 3, 3, 3, 6, 7, 8, 8, 8, 11
H.Gypsy Persian: 0, 1, 1, 1, 4, 5, 5, 7, 8, 8, 8, 11
Japanese A: 0, 1, 1, 1, 1, 5, 5, 7, 8, 8, 8, 8
Japanese B: 0, 0, 2, 2, 2, 5, 5, 7, 8, 8, 8, 8
Persian: 0, 1, 1, 1, 4, 5, 6, 6, 8, 8, 8, 11
Prometheus: 0, 0, 2, 2, 4, 4, 6, 6, 6, 9, 10, 10
Six Tone Symetrical: 0, 1, 1, 1, 4, 5, 5, 5, 8, 9, 9, 9
Super Locrian: 0, 1, 1, 3, 4, 4, 6, 6, 8, 8, 10, 10
Wholetone: 0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10
"""



def abbreviate(x):
   
"""
    A small routine to automatically derive an abbreviation from the long name.
    """

   
try:
        first
,last = x.split(' ')
       
if len(last)>2:
           
return "%s%s"%(first[0],last[0:3])
       
elif len(last)==1:
           
return "%s%s"%(first[0:3],last[0])
       
else:
           
return first[0:4]
   
except:
       
return x[0:4]


def chromatic_to_voltage_table(degrees):
   
"""
    Convert a list of chromatic scale degrees to a binary voltage table.
    """

   
# roll out the degrees to 100 entries
    unrolled
= [degrees[i%len(degrees)]+12*(i/12) for i in range(100)]
   
# create a list of codes for each semitone
    codes
= [int(round(x*8000/12.0)) for x in unrolled]
   
# do not exceed the max for unsigned shorts
    codes
= [x if x<(1<<16)-1 else (1<<16)-1 for x in codes]
   
# convert the python list to a binary array
    table
= (ctypes.c_uint16 * len(codes))(*codes)
   
return table


# parse the input data, line by line
for item in input_data.split('\n'):
   
if len(item)==0: continue
    scale
= item.split(':')
    scale_name
= scale[0]
    scale_degrees
= [int(x) for x in scale[1].split(',')]
    scale_short_name
= abbreviate(scale_name)
   
print scale_name, "-->", scale_short_name
   
print scale_degrees
    table
= chromatic_to_voltage_table(scale_degrees)
   
# Write the binary array out to a binary file.
   
# Note: The '-P' at the end of the filename indicates a pitched table and thus voltages
   
#       will be shown as note values rather than numeric values.
   
with open(scale_short_name+"-P.BIN","wb") as f:
        f
.write(table)
        f
.close()
   



Ben Armstrong

unread,
Feb 13, 2017, 9:41:19 AM2/13/17
to odevices
OK, I can see the benefit of having the out of scale notes rounded up or down, however one of the things I liked the idea of was having the index being the scale degree. To do this, I think I need to create completely new voltage tables.

At the moment this seems to be a very manual process

Am wondering whether a bit of script could be used to take a CSV file that could be created in something like Excel and turn it into the binary array?

I found this code on GitHub that might do the trick

https://gist.github.com/jaywilliams/385876

Thoughts?

Brian@O|D

unread,
Feb 13, 2017, 10:10:38 PM2/13/17
to odevices
Sure I can write a quick python script that takes as input a CSV file and outputs a voltage table file (^_^)y

May I ask you to provide me with an example CSV file that shows how you would like to specifiy the voltage table contents?

Ben Armstrong

unread,
Feb 13, 2017, 11:11:07 PM2/13/17
to Brian@O|D, odevices
I was thinking something as simple as

"Index","voltage"
1,0.000
2,0.0833
3,0.1667
etc.

Are there any other fields needed? Note labels?

Ben

Ps. You are awesome



______________
Sent from iPhone - please excuse  brevity and autocorrects 

On Feb 13, 2017, at 7:10 PM, Brian@O|D <clar...@orthogonaldevices.com> wrote:

Sure I can write a quick python script that takes as input a CSV file and outputs a voltage table file (^_^)y

May I ask you to provide me with an example CSV file that shows how you would like to specifiy the voltage table contents?

--
You received this message because you are subscribed to a topic in the Google Groups "odevices" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/odevices/brTAp94g6OY/unsubscribe.
To unsubscribe from this group and all its topics, send an email to odevices+u...@googlegroups.com.
To post to this group, send email to odev...@googlegroups.com.
Visit this group at https://groups.google.com/group/odevices.
To view this discussion on the web visit https://groups.google.com/d/msgid/odevices/94007967-f788-477b-963f-6ac55847a2c3%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Brian@O|D

unread,
Feb 18, 2017, 7:10:47 AM2/18/17
to odev...@googlegroups.com, clar...@orthogonaldevices.com
Below (and attached) is a script that converts voltage table in CSV format to the binary format needed by the ER-102.  Here is the usage:

This script converts a CSV file containing 2 columns (index and voltage) to the ER-102 voltage table format. Indices should extend from 0 to 99 and voltages should extend from 0 to 8.192.  The output file name should follow the format XXXX-P.BIN for tables that should be displayed as pitches by default, otherwise use XXXX.BIN.
 
python csv-to-voltage-table.py -i <inputfile> -o <outputfile>
 
EXAMPLE:
python csv-to-voltage-table.py -i test.csv -o TEST-P.BIN



import ctypes
import csv
import sys
import getopt

def printHelp():
  print "This script converts a CSV file containing 2 columns (index and voltage) to the ER-102 voltage table format. Indices should extend from 0 to 99 and voltages should extend from 0 to 8.192.  The output file name should follow the format XXXX-P.BIN for tables that should be displayed as pitches by default, otherwise use XXXX.BIN."
  print
  print 'python csv-to-voltage-table.py -i <inputfile> -o <outputfile>'
  print
  print "EXAMPLE:"
  print "python csv-to-voltage-table.py -i test.csv -o TEST-P.BIN"
  print

def main(argv):
  inputfile = None
  outputfile = None
  try:
    opts, args = getopt.getopt(argv,"hi:o:",["ifile=","ofile="])
  except getopt.GetoptError:
    print 'csv-to-voltage-table.py -i <inputfile> -o <outputfile>'
    sys.exit(2)  
  for opt, arg in opts:
    if opt == '-h':
      printHelp()
      sys.exit()
    elif opt in ("-i", "--ifile"):
      inputfile = arg
    elif opt in ("-o", "--ofile"):
      outputfile = arg

  if inputfile==None or outputfile==None:
    print "Error: no input or output file specified."
    print
    printHelp()
    sys.exit(2)
    
  # default voltage table
  voltages = [0]*100

  # parse the CSV file
  print "Reading:", inputfile
  count = 0
  with open(inputfile) as inputstream:
    reader = csv.DictReader(inputstream)
    for row in reader:
      # change field names to lower case
      row =  {k.lower(): v for k, v in row.items()}
      i = int(row["index"])
      v = float(row["voltage"])
      if i >= 0 and i < 100:
        count = count + 1
        voltages[i] = v
  print "Parsed",count,"entries."

  # output the voltage table as a flat binary array of unsigned shorts
  codes = [int(round(x*8000)) for x in voltages]
  
# do not exceed the max for unsigned shorts
  codes = [x if x<(1<<16)-1 else (1<<16)-1 for x in codes]
  # convert the python list to a binary array
  table = (ctypes.c_uint16 * len
(codes))(*codes)

  print "Writing:", outputfile
  with open(outputfile,"wb") as outputstream:
    outputstream.write(table)

if __name__ == "__main__":
   main(sys.argv[1:])





csv-to-voltage-table.py
test.csv

Ben Armstrong

unread,
Feb 18, 2017, 11:38:15 AM2/18/17
to Brian@O|D, odevices
Thanks Brian, will give this a test this afternoon. 

I continue to be amazed at the level of support you provide for your modules. You really set the bar! 


______________
Sent from iPhone - please excuse  brevity and autocorrects 
--
You received this message because you are subscribed to a topic in the Google Groups "odevices" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/odevices/brTAp94g6OY/unsubscribe.
To unsubscribe from this group and all its topics, send an email to odevices+u...@googlegroups.com.
To post to this group, send email to odev...@googlegroups.com.
Visit this group at https://groups.google.com/group/odevices.

For more options, visit https://groups.google.com/d/optout.
<csv-to-voltage-table.py>
<test.csv>
Reply all
Reply to author
Forward
0 new messages