Class: MusicSetTheory::Temperament::Temperament

Inherits:
Object
  • Object
show all
Defined in:
lib/music_set_theory/temperament.rb,
lib/music_set_theory/temperament.rb,
lib/music_set_theory/temperament.rb,
lib/music_set_theory/temperament.rb

Overview

A musical temperament is used to define the possible keys in music, and also the positions of keys relevant to each other. Our primary example is the western or chromatic temperament, with 12 possible keys. A temperament also defines which of the keys can be represented as naturals (like "C", "D" and "E"), and which need to be represented with accidentals (like "C#" and "Db".

Doing anything more at the moment is overkill. For example, we are not intereted in the possible frequencies for notes of a given key.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(no_keys = CHROM_SIZE, nat_keys = CHROM_NAT_NOTES, nat_key_posn = CHROM_NAT_NOTE_POS) ⇒ Temperament

Initialiser.

Args

no_keys

the number of keys in the temperament. !number of semitones in one octave. ex. 12.

nat_keys

an array consisting of the names of the natural (unsharped or unflattened) keys in the temperament. ex. ["C", "D", "E", "F", "G", "A", "B"]

nat_key_posn

the position of the natural keys in the temperament. These should correspond to the elements in nat_keys. Positions are calculated base zero. ex. [ 0, 2, 4, 5, 7, 9, 11]

def initialize( no_keys, nat_keys, nat_key_posn )



412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/music_set_theory/temperament.rb', line 412

def initialize( no_keys       = CHROM_SIZE,
                nat_keys      = CHROM_NAT_NOTES,
                nat_key_posn  = CHROM_NAT_NOTE_POS )
  #
  self.no_keys      = no_keys
  self.nat_keys     = nat_keys
  self.nat_key_posn = nat_key_posn

  # Extra variable: no_nat_keys: number of natural keys.
  #self.no_nat_keys = min(len(nat_keys), len(nat_key_posn))
  self.no_nat_keys = [nat_keys.size, nat_key_posn.size].min

  # Extra variable: nat_key_pos_lookup:
  # key -> position (e.g. A -> 9, C->0).
  self.nat_key_pos_lookup = {}

  # Extra variable: post_lookup_nat_key: position
  #   -> key/None (e.g., 9->A, 0->C).
  self.pos_lookup_nat_key = {}

  # Extra variable: nat_key_lookup_order: looks up order of
  # nat_keys (e.g, C->0, D->1... B->6). Reverse translation
  # available by self.nat_keys[order].
  self.nat_key_lookup_order = {}

  #
  for i in 0...self.no_nat_keys
    self.nat_key_pos_lookup[nat_keys[i]]      = nat_key_posn[i]
    self.pos_lookup_nat_key[nat_key_posn[i]]  = nat_keys[i]
    self.nat_key_lookup_order[nat_keys[i]]    = i
  end

  # self.parsenote = re.compile(RE_NOTEPARSE);
  self.parsenote = Regexp.compile(RE_NOTEPARSE)

  #  Useful for dictionary lookup later.
  #self.seq_maps = seq_dict([NSEQ_SCALE, NSEQ_CHORD], self);
  self.seq_maps = SeqDict.new([NSEQ_SCALE, NSEQ_CHORD], self)

end

Instance Attribute Details

#nat_key_lookup_orderObject

Returns the value of attribute nat_key_lookup_order.



392
393
394
# File 'lib/music_set_theory/temperament.rb', line 392

def nat_key_lookup_order
  @nat_key_lookup_order
end

#nat_key_pos_lookupObject

Returns the value of attribute nat_key_pos_lookup.



391
392
393
# File 'lib/music_set_theory/temperament.rb', line 391

def nat_key_pos_lookup
  @nat_key_pos_lookup
end

#nat_key_posnObject

Returns the value of attribute nat_key_posn.



390
391
392
# File 'lib/music_set_theory/temperament.rb', line 390

def nat_key_posn
  @nat_key_posn
end

#nat_keysObject

Returns the value of attribute nat_keys.



390
391
392
# File 'lib/music_set_theory/temperament.rb', line 390

def nat_keys
  @nat_keys
end

#no_keysObject

Returns the value of attribute no_keys.



390
391
392
# File 'lib/music_set_theory/temperament.rb', line 390

def no_keys
  @no_keys
end

#no_nat_keysObject

Returns the value of attribute no_nat_keys.



391
392
393
# File 'lib/music_set_theory/temperament.rb', line 391

def no_nat_keys
  @no_nat_keys
end

#parsenoteObject

Returns the value of attribute parsenote.



393
394
395
# File 'lib/music_set_theory/temperament.rb', line 393

def parsenote
  @parsenote
end

#pos_lookup_nat_keyObject

Returns the value of attribute pos_lookup_nat_key.



392
393
394
# File 'lib/music_set_theory/temperament.rb', line 392

def pos_lookup_nat_key
  @pos_lookup_nat_key
end

#seq_mapsObject

Returns the value of attribute seq_maps.



394
395
396
# File 'lib/music_set_theory/temperament.rb', line 394

def seq_maps
  @seq_maps
end

Instance Method Details

#add_elem(elem, nseq_type, name_s, abbrv_s, seqpos) ⇒ Object

This adds an element to the dictionary inside the temperament.

Args

elem

the element to add to the dictionary .

nseq_type

the type of the elemenet (such as scale or chord).

name_s

a string, or a sequence of strings. This provides names as keys that map onto elem.

abbrv_s

a string, or a sequence of strings. This provides abbreviations as keys that map onto elem.

seqpos

a sequence. A tuple form will be used as a key that maps onto elem.

If nseq_type is not associated with any of the sub-dictionaries in the dictionary in the temperament, then this function exits.



753
754
755
# File 'lib/music_set_theory/temperament.rb', line 753

def add_elem( elem, nseq_type, name_s, abbrv_s, seqpos )
  self.seq_dict.add_elem(elem, nseq_type, name_s, abbrv_s, seqpos)
end

#check_nseqby_abbrv(nseq_type, abbrv) ⇒ Object

Checks if there is a noteseq with a given abbreviation.



769
770
771
# File 'lib/music_set_theory/temperament.rb', line 769

def check_nseqby_abbrv( nseq_type, abbrv )
  self.seq_dict.check_nseqby_abbrv(nseq_type, abbrv)
end

#check_nseqby_name(nseq_type, name) ⇒ Object

Checks if there is a noteseq with a given name.



764
765
766
# File 'lib/music_set_theory/temperament.rb', line 764

def check_nseqby_name( nseq_type, name )
  self.seq_dict.check_nseqby_name(nseq_type, name)
end

#check_nseqby_seqpos(nseq_type, seqpos) ⇒ Object

Checks if there is a noteseq with a given sequence position.



774
775
776
# File 'lib/music_set_theory/temperament.rb', line 774

def check_nseqby_seqpos( nseq_type, seqpos )
  self.seq_dict.check_nseqby_seqpos(nseq_type, seqpos)
end

#check_nseqby_subdict(nseq_type) ⇒ Object

Checks if there is a subdictionary associated with nseq_type.



759
760
761
# File 'lib/music_set_theory/temperament.rb', line 759

def check_nseqby_subdict( nseq_type )
  self.seq_dict.check_nseqby_subdict(nseq_type)
end

#get_key_of_pos(pos, desired_nat_note = nil, sharp_not_flat = nil) ⇒ Object Also known as: key_of_pos, key_of

Given a position in the temperament, this function attempts to return the "best" key name corresponding to it. This is not a straight-forward reverse of get_pos_of_key, as there may be two different keynames for the same position, with one preferred. For example, "C# and "Db" are the same key, but "C# is preferred in an A major scale. Fortunately, arguments are provided to indicate the programmer's preference.

Args

pos

the position inside the temperament.

desired_nat_note

the preferred natural key to start the key name. For example "C" makes the function return "C#" instead of "Db". If nil, then the preference depends on...

sharp_not_flat

if true, returns the sharpened accidental form

(e.g., "C#"); if false, returns the flattened accidental form (i.e., "Db").

Returns



587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
# File 'lib/music_set_theory/temperament.rb', line 587

def get_key_of_pos( pos, desired_nat_note = nil, sharp_not_flat = nil )

  # accdtls: number of sharps or flats to add to the output string.

  if desired_nat_note
    accdtls = pos - self.nat_key_pos_lookup[desired_nat_note]
    accdtls = (accdtls % self.no_keys)

    if accdtls > (self.no_keys / 2)
      accdtls = accdtls - self.no_keys
    end

    if accdtls > 0
      return desired_nat_note + (M_SHARP * accdtls)
    elsif accdtls < 0
      return desired_nat_note + (M_FLAT * (-1 * accdtls))
    else
      return desired_nat_note
    end
  else
    accdtls = 0
    # if (pos % self.no_keys) in self.pos_lookup_nat_key
    if self.pos_lookup_nat_key.key?( pos % self.no_keys )
      return self.pos_lookup_nat_key[pos % self.no_keys]

    elsif sharp_not_flat
      while accdtls < self.no_keys
        accdtls = accdtls - 1
        #if ((pos + accdtls) % self.no_keys) in self.pos_lookup_nat_key
        if self.pos_lookup_nat_key.key?((pos + accdtls) % self.no_keys)
          return self.pos_lookup_nat_key[
            (pos + accdtls) % self.no_keys] + (M_SHARP * (-1 * accdtls))
        end
      end

    else
      while accdtls < self.no_keys
        accdtls = accdtls + 1
        #if ((pos + accdtls) % self.no_keys) in self.pos_lookup_nat_key:
        if self.pos_lookup_nat_key.key?((pos + accdtls) % self.no_keys)
          return self.pos_lookup_nat_key[
            (pos + accdtls) % self.no_keys] + (M_FLAT * accdtls)
        end
      end

    end
  end

  return nil
end

#get_keyseq_notes(note_seq) ⇒ Object

The reverse of get_note_sequence().

Args

note_seq

a sequence of notes (note_seq)

Returns

[base key, position sequence]

Examples

["C", "D"] =>["C", [0, 2]] ["D", "C#", "E"] =>["D", [0, 11, 2]]

this is returned by the function.

See Also

  • #get_note_sequence()


709
710
711
712
713
714
715
716
# File 'lib/music_set_theory/temperament.rb', line 709

def get_keyseq_notes( note_seq )
  base_key = note_seq[0]
  base_pos = self.get_pos_of_key(base_key)
  pos_seq  = note_seq.map{|i|
               (self.get_pos_of_key(i) - base_pos) % self.no_keys }

  [base_key, pos_seq]
end

#get_note_sequence(key, pos_seq, nat_pos_seq = nil, sharp_not_flat = nil) ⇒ Object

This function takes a key and a sequence of positions relative to it. It returns a sequence of notes.

Args

key

the starting key; examples are "C", "C#" and "Db".

pos_seq

a list of positions relative to it in the temperament base 0). For example, in the Western/Chromatic temperament, a key of "C" and a sequence of [0, 1] returns ["C", "C#].

nat_pos_seq

a list of numbers. These are used to calculate the desired natural notes produced from the corresponding positions in pos_seq. A number of 0 means to use the same natural note as in key, a number of 1 means using the next natural note, and so on. Examples for the Western/Chromatic scale: get_note_sequence("C", [0, 1], [0, 0]) =>["C", "C#"] get_note_sequence("C", [0, 1], [0, 1]) =>["C", "Dbb"] get_note_sequence("C", [0, 1], [0, 6]) =>["C", "B##"]

sharp_not_flat

if nat_pos_seq is None, indicates whether notes with accidentals are preferred to have sharps (True) or flats (False)

Returns

See Also

  • #get_keyseq_notes()


664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
# File 'lib/music_set_theory/temperament.rb', line 664

def get_note_sequence( key, pos_seq,
                       nat_pos_seq = nil, sharp_not_flat = nil )
  ret = nil
  base_key_pos   = self.get_pos_of_key(key)
  #result_pos_seq = [((base_key_pos + i) % self.no_keys) for i in pos_seq]
  result_pos_seq = pos_seq.map{|i| (base_key_pos + i) % self.no_keys }

  if nat_pos_seq
    result_seq = []
    desired_nat_key = self.note_parse(key)[0]
    desired_nat_key_posn = self.nat_keys.index(desired_nat_key)

    #for i in range(min(len(nat_pos_seq), len(result_pos_seq))):
    for i in 0...([nat_pos_seq.size, result_pos_seq.size].min)
      des_nat_note = self.nat_keys[
        (desired_nat_key_posn + nat_pos_seq[i]) % self.no_nat_keys]
      des_key = self.get_key_of_pos(result_pos_seq[i], des_nat_note)
             result_seq.append(des_key)
    end
    ret = result_seq

  else
    # return [self.get_key_of_pos(i, None, sharp_not_flat) for i in
    #   result_pos_seq];
    ret = result_pos_seq.map{|i|
            self.get_key_of_pos(i, nil, sharp_not_flat) }
  end

  ret
end

#get_nseqby_abbrv(abbrv, nseq_type) ⇒ Object

Looks up a noteseq (or anything else) by abbreviation.



788
789
790
791
792
793
794
# File 'lib/music_set_theory/temperament.rb', line 788

def get_nseqby_abbrv( abbrv, nseq_type )
     if self.seq_maps.check_nseqby_abbrv(nseq_type, abbrv)
       self.seq_maps.get_nseqby_abbrv(abbrv, nseq_type)
     else
       nil
     end
end

#get_nseqby_name(name, nseq_type) ⇒ Object

Looks up a noteseq (or anything else) by name.



779
780
781
782
783
784
785
# File 'lib/music_set_theory/temperament.rb', line 779

def get_nseqby_name( name, nseq_type )
   if self.seq_dict.check_nseqby_name(nseq_type, name)
     self.seq_dict.get_nseqby_name(name, nseq_type)
   else
     nil
   end
end

#get_nseqby_seqpos(seqpos, nseq_type) ⇒ Object

Looks up a noteseq (or anything else) by sequence position.



797
798
799
800
801
802
803
# File 'lib/music_set_theory/temperament.rb', line 797

def get_nseqby_seqpos( seqpos, nseq_type )
  if self.seq_maps.check_nseqby_seqpos(nseq_type, seqpos)
    self.seq_maps.get_nseqby_seqpos(seqpos, nseq_type)
  else
    nil
  end
end

#get_pos_of_key(key, debug_f: false) ⇒ Object Also known as: pos_of_key, position_of, pos_of

Returns the position of the key in the temperament.

Args

key

key note. ex. "C", "D#", "Cb"

Returns

position of the key in the unit of halftone. ex. key: "C" =>0, key: "D#" =>3, key: "Cb" =>-1

See Also

  • #get_key_of_pos()


557
558
559
560
561
562
563
564
565
# File 'lib/music_set_theory/temperament.rb', line 557

def get_pos_of_key( key, debug_f: false )
  key_parsed = self.note_parse(key)
  $stderr.puts "key_parsed: #{key_parsed}"  if debug_f
  $stderr.puts "nat_key_pos_lookup: #{self.nat_key_pos_lookup}" if \
    debug_f

  ret = self.nat_key_pos_lookup[key_parsed[0]] + key_parsed[1]
  ret
end

#note_parse(key, debug_f: false) ⇒ Object

Parses the name of a key into the tuple (natural key name, sharp_or_flat count).

Args

key

note.

Returns

an array: [ , ]. For example "C#" is parsed into ["C", 1], "Db" is parsed into ["D", -1], and "E" is parsed into ["E", 0]. As the reader may gather, negative numbers are used for flattened notes.



515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
# File 'lib/music_set_theory/temperament.rb', line 515

def note_parse( key, debug_f: false )
  noteMatch = self.parsenote.match(key)
  $stderr.puts "{#{__method__}} key (#{key.length}): #{key}"  if debug_f
  $stderr.puts "  =>#{noteMatch[:basenotename].chars}"        if debug_f
  return nil if !(noteMatch)

  #
  # $stderr.puts "note_match: #{noteMatch}:#{noteMatch.class}"
  # noteGroup = noteMatch.group('basenotename')
  noteGroup = noteMatch[:basenotename].chars
  baseNote  = noteGroup[0];
  $stderr.puts "key: #{key}::" +
    " note_group: #{noteGroup.inspect}:#{noteGroup.class}," +
    " chars(#{noteGroup.size}): #{noteGroup};" +
    " base_note: #{baseNote}"  if debug_f

  #
  flatSharpAdj = 0
  for chr in noteGroup[1..]
    $stderr.puts "{#{__method__}} adj: #{chr}"  if debug_f
    if /#{chr}/ =~ (M_FLAT + MNU_FLAT)
      flatSharpAdj += -1
    end
    if /#{chr}/ =~ (M_SHARP + MNU_SHARP)
      flatSharpAdj += +1
    end
  end

  [ baseNote, flatSharpAdj,]
end