1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
|
#--
# This file is part of Sonic Pi: http://sonic-pi.net
# Full project source: https://github.com/samaaron/sonic-pi
# License: https://github.com/samaaron/sonic-pi/blob/master/LICENSE.md
#
# Copyright 2013, 2014, 2015, 2016 by Sam Aaron (http://sam.aaron.name).
# All rights reserved.
#
# Permission is granted for use, copying, modification, and
# distribution of modified versions of this work as long as this
# notice is included.
#++
module SonicPi
class Note
class InvalidNoteError < ArgumentError ; end ;
class InvalidOctaveError < ArgumentError ; end ;
NOTES_TO_INTERVALS =
{cf: -1, CF: -1, Cf: -1, cF: -1,
cb: -1, CB: -1, Cb: -1, cB: -1,
c: 0, C: 0,
cs: 1, CS: 1, cS: 1, Cs: 1,
df: 1, DF: 1, Df: 1, dF: 1,
db: 1, DB: 1, Db: 1, dB: 1,
d: 2, D: 2,
eb: 3, EB: 3, Eb: 3, eB: 3,
ef: 3, EF: 3, Ef: 3, eF: 3,
ds: 3, DS: 3, Ds: 3, dS: 3,
e: 4, E: 4,
fb: 4, FB: 4, Fb: 4, fB: 4,
ff: 4, FF: 4, Ff: 4, fF: 4,
f: 5, F: 5,
es: 5, ES: 5, Es: 5, eS: 5,
fs: 6, FS: 6, Fs: 6, fS: 6,
gb: 6, GB: 6, Gb: 6, gB: 6,
gf: 6, GF: 6, Gf: 6, gF: 6,
g: 7, G: 7,
gs: 8, GS: 8, Gs: 8, gS: 8,
ab: 8, AB: 8, Ab: 8, aB: 8,
af: 8, AF: 8, Af: 8, aF: 8,
a: 9, A: 9,
bb: 10, BB: 10, Bb: 10, bB: 10,
bf: 10, BF: 10, Bf: 10, bF: 10,
as: 10, AS: 10, As: 10, aS: 10,
b: 11, B: 11,
bs: 12, BS: 12, Bs: 12, bS: 12}
INTERVALS_TO_NOTES = {
0 => :C,
1 => :Cs,
2 => :D,
3 => :Eb,
4 => :E,
5 => :F,
6 => :Fs,
7 => :G,
8 => :Ab,
9 => :A,
10 => :Bb,
11 => :B}
DEFAULT_OCTAVE = 4
MIDI_NOTE_RE = /\A:?(([a-gA-G])([sSbBfF]?))([-]?[0-9]*)\Z/
@@notes_cache = Hash.new
@@midi_string_cache = Hash.new {|h, k| h[k] = Hash.new }
@@midi_to_note_cache = Hash.new
def self.resolve_note(n, o=nil)
raise Exception.new("resolve_note argument must be a valid note. Got nil.") if(n.nil?)
n_key = n.is_a?(String) ? n.to_sym : n
return @@midi_to_note_cache[@@midi_string_cache[n_key][o]] if @@midi_string_cache[n_key][o]
note = self.new(n_key, o)
case n_key
when Numeric
@@midi_string_cache[n_key][o] = note.midi_string
else
n_key = n.to_s
downcase = n_key.downcase
[n_key.upcase.to_sym, downcase.to_sym, downcase.capitalize.to_sym].each do |key|
@@midi_string_cache[key][o] = note.midi_string
end
end
@@midi_to_note_cache[note.midi_string] = note unless @@midi_to_note_cache.has_key?(note.midi_string)
note
end
def self.resolve_midi_note_without_octave(n)
return @@notes_cache[n] if @@notes_cache[n]
note = case n
when Symbol, String
self.resolve_note(n).midi_note
when NilClass
nil
when Numeric
#don't cache numbers
#short-circuit and return
return n
end
@@notes_cache[n] = note
note
end
def self.resolve_midi_note(n, o=nil)
return resolve_midi_note_without_octave(n) unless o
n = resolve_midi_note_without_octave(n)
raise InvalidOctaveError, "Invalid octave: #{o.inspect}, expecting a number" unless o.is_a? Numeric
n = n % 12
o = o.to_i * 12
n + o + 12
end
def self.resolve_note_name(n, o=nil)
note = resolve_midi_note(n, o)
note = note.to_i % 12
INTERVALS_TO_NOTES[note]
end
attr_reader :pitch_class, :octave, :interval, :midi_note, :midi_string
def initialize(n, o=nil)
if n.is_a? Numeric
o = (n / 12).to_i - 1
n = n % 12
n = Note.resolve_note_name(n.to_f)
end
orig_n = n
n = n.to_s
m = MIDI_NOTE_RE.match n
raise InvalidNoteError, "Invalid note: #{orig_n.inspect}" unless m
@pitch_class = "#{m[2].capitalize}#{unify_sharp_flat_modifier(m[3])}".to_sym
if o
raise InvalidOctaveError, "Invalid octave: #{o.inspect}, expecting a whole number such as 3 or 4!" unless o.is_a? Integer
@octave = o.to_i
else
@octave = m[4].empty? ? DEFAULT_OCTAVE : m[4].to_i
end
@interval = NOTES_TO_INTERVALS[m[1].downcase.to_sym]
raise InvalidNoteError, "Invalid note: #{orig_n.inspect}" unless @interval
@midi_note = (@octave * 12) + @interval + 12
@midi_string = "#{@pitch_class.capitalize}#{@octave}"
end
def to_h
{:pitch_class => @pitch_class, :octave => @octave, :interval => @interval, :midi_note => @midi_note, :midi_string => @midi_string}
end
def to_s
"#<SonicPi::Note :#{@midi_string}>"
end
def inspect
to_s
end
private
def unify_sharp_flat_modifier(mod)
m = mod.downcase
return "b" if m == "f"
m
end
end
end
|