예제 #1
0
def metric_denom(degree):
    '''
    .. py:function:: metric_denom(degree)
    
        Calculate a metric for the consonance heatmap. This metric is just the 
        denominator of the normalized ratio. It is also the default
        metric if none is specified.
        
        :param scale: The degree (a frequency ratio)
        :returns: The metric
    '''
    normalized_degree = normalize_interval(degree)
    #y = sp.fraction(normalized_degree)[0] - sp.fraction(normalized_degree)[1]
    y = sp.fraction(normalized_degree)[1]
    return y
예제 #2
0
def metric_denom(ratio):
    '''Function that computes the denominator of the normalized ratio

    Parameters
    ----------
    ratio: float

    Returns
    -------
    y : float
        denominator of the normalized ratio
    '''
    ratio = sp.Rational(ratio).limit_denominator(10000)
    normalized_degree = normalize_interval(ratio)
    y = int(sp.fraction(normalized_degree)[1])
    return y
예제 #3
0
def sum_q_for_all_intervals(scale):
    '''    
    Calculate a metric for a scale.
    
    :param scale: The scale (i.e., a list of ``Rational`` s)
    :returns: The metric.
    
    Metric 5 is an estimate of scale consonance. It is summation
    of the denominators of the normalized distinct ratios
    of the scale.
    
    Smaller values are more consonant.
    '''
    m5 = np.sum([
        sp.fraction(normalize_interval(x))[1]
        for x in distinct_intervals(scale)
    ])

    return {"sum_q_for_all_intervals": m5}
예제 #4
0
def create_euler_fokker_scale(intervals,
                              multiplicities,
                              octave=2,
                              normalize=True):
    '''    
    Create a scale in the Euler-Fokker Genera
    
    :param intervals: The factors to use for the construction (usually prime
        numbers)
    :param multiplicities: The multiplicities of the factors (see below)
    :param octave: The formal octave
    :param normalize: If ``True``, normalize the intervals to the octave.
    
    ``intervals`` and ``multiplicities`` should both be lists of equal length.
    The entries in ``multiplicities`` give the number of each factor to use.
    Therefore the following:
    
    .. code::
    
        intervals     = [3,5,7]
        multiplicities = [1,1,1]
        scale         = create_euler_fokker_scale(intervals, multiplicities)
        
    Will create a scale with one 3, one 5, and one 7 as generators.
    
    The above will produce the following scale:
    
    .. math::
    
        \\left [ 1, \\frac{35}{32}, \\frac{5}{4}, \\frac{21}{16}, \\frac{3}{2}, \\frac{105}{64}, 
        \\frac{7}{4}, \\frac{15}{8}, 2\\right ]
        
    Also note that the two statements will generate the same output:
    
    .. code::
    
        intervals     = [3,5,7]
        multiplicities = [2,2,1]
        scale1        = create_euler_fokker_scale(intervals, multiplicities)

        intervals     = [3,3,5,5,7]
        multiplicities = [1,1,1,1,1]
        scale2        = create_euler_fokker_scale(intervals, multiplicities)
        
        scale1 == scale2
        True
    '''

    output = []
    for index in range(len(intervals)):
        output = output + [intervals[index]] * multiplicities[index]
        output = [sp.Integer(x) for x in output]

    potential = list(
        itertools.chain(*[[x for x in itertools.combinations(output, r)]
                          for r in range(1,
                                         len(output) + 1)]))
    output = []
    for item in potential:
        if len(item) == 0:
            output = output + [item[0]]
        else:
            output = output + [reduce(operator.__mul__, item)]

    if normalize:
        output = [sp.Integer(1)
                  ] + [normalize_interval(x, octave)
                       for x in output] + [sp.Integer(octave)]
    else:
        output = [sp.Integer(1)] + [x for x in output] + [sp.Integer(octave)]

    output = sorted(set(output))
    return output
예제 #5
0
def create_equal_interval_scale(generator_interval, scale_size=12, number_down_intervals=6, 
                          epsilon=None, sort=True, octave=2, remove_duplicates=True, 
                          normalize=True):
    '''    
    Create a scale with equal-interval tuning
    
    :param generator_interval: The interval to use for generation (``sympy`` value)
    :param scale_size: The number of degrees in the scale
    :param number_down_intervals: The number of inverted intervals to use in
        scale construction.
    :param epsilon: Rounding parameter. If set to ``None`` no rounding is
        done. Otherwise the scale degrees are rounded to the nearest
        epsilon
    :param sort: If ``True``, sort the output by degree size
    :param octave: The formal octave
    :param remove_duplicates: If ``True`` remove duplicate entries
    :param normalize: IF ``True``, normalize the degrees to the octave
    
    In general one should keep epsilon at ``None`` and perform and
    rounding outside the function.
    
    This is a base function from which several other scales are derived,
    including:
    
    * The Pythagorean scale
        A scale with a perfect
        fifth (3/2) as the generating interval
        
        .. math::
        
            P_5 = \\frac{3}{2}
            
    * The quarter-comma meantone scale
        A scale in which the generating
        interval is a perfect fifth narrowed by one quarter of syntonic
        comma
        
        .. math::
        
            P_5 = \\frac{\\frac{3}{2}}{\\sqrt[4]{\\frac{81}{80}}}
            
    * EDO Scales
        EDO scales can be generated from an appropriate selection of the
        fifth. For example, the 12-TET scale would use the fifth:
        
        .. math::
        
            P_5 = \\sqrt[\\frac{7}{12}]{2}
    '''
    down_intervals = number_down_intervals + 1
    up_intervals   = scale_size - down_intervals + 1
    r_5 = generator_interval
    output = []
    for index in range(up_intervals):
        x = r_5 ** index
        if normalize:
            output = output + [normalize_interval(x, octave)]
        else:
            output = output + [x]
    for index in range(down_intervals):
        x = (1/r_5) ** index
        if normalize:
            output = output + [normalize_interval(x, octave)]
        else:
            output = output + [x]
    output = output + [sp.Integer(octave)]
    if epsilon is not None:
        output =  list(set([round(y/epsilon)* epsilon for y in output]))
    else:
        output =  list(output)
    if sort:
        output = sorted(output)
        if remove_duplicates:
            output = sorted(set(output))
    return output
예제 #6
0
def create_harmonic_scale(first_harmonic,
                          last_harmonic,
                          normalize=True,
                          octave=2):
    '''    
    Create a harmonic scale
    
    :param first_harmonic: The first harmonic
    :param last_harmonic: The last harmonic
    :param normalize: If true, normalize the scale to an octave 
                      (2/1 by default, otherwise taken from ``octave``)
    :param octave: The definition of the formal octave.
    :returns: The scale
    
    As an example of use, a normalized scale constructed from harmonics
    3 to 20:
    
    .. code::
    
        scale = create_harmonic_scale(3,20)
        
    which yields:
    
    .. math::
    
        \\left [ 1, \\frac{13}{12}, \\frac{7}{6}, \\frac{5}{4}, \\frac{4}{3}, 
        \\frac{17}{12}, \\frac{3}{2}, \\frac{19}{12}, \\frac{5}{3}, \\frac{11}{6}, 2\\right ]
        
    To create a non-normalized scale:
    
    .. code::
    
        scale = create_harmonic_scale(3,10, normalize=False)
        
    which yields:
    
    .. math:: 
    
        \\left [ 1, \\frac{4}{3}, \\frac{5}{3}, 2, \\frac{7}{3}, \\frac{8}{3}, 3, 
        \\frac{10}{3}\\right ]
    '''
    r = [sp.Integer(1) + sp.Integer(1) * (i) for i in range(last_harmonic)]
    r = r[first_harmonic - 1:]
    r = [i / r[0] for i in r]
    output = []
    for interval in r:
        if normalize:
            interval = normalize_interval(interval, octave)
        output = output + [interval]
    output = sorted(list(set(output)))

    if normalize:
        output = [sp.Integer(1)
                  ] + [normalize_interval(x, octave)
                       for x in output] + [sp.Integer(octave)]
    else:
        output = [sp.Integer(1)] + [x for x in output] + [sp.Integer(octave)]

    output = sorted(set(output))

    return output
예제 #7
0
def create_lucy_tuning_spiral(scale_size=44,
                              number_fourths=22,
                              sort=False,
                              octave=2):
    '''    
    Create the Lucy scale tuning spiral. The default values were chosen to
    match the published numbers
    
    :param scale_size: The number of intervals to produce
    :param number_fourth: The number of fourths to use
    :param sort: If True, the output will be sorted on scale size
    :param octave: The formal octave
    :returns: A list of tuples, the first member of which is the assigned symbol
        for the degree, and the second is the precise degree value (``sympy`` value).
    
    The tuning spiral is created by defining the Perfect Fifth and
    Perfect Fourth in terms of Lucy steps:
    
    .. math ::
    
        \\begin{align}
        P_5 & = L^3 \\cdot s \\cr
        P_4 &= L^2 \\cdot s
        \\end{align}
        
    The fourths and fifths are used as generators; in the default case, 22 of each are
    used to generate the target tones.
    
    **Important Note** According to the documentation of the scale, the fourths and
    fifths are taken in an opposite sense (which is to say one goes clockwise on the
    tuning circle, one goes counter-clockwise). However, based upon the published
    values *this is not really the case*. In this code both the fourths and the fifths
    are propagated in the same direction (no inversion is taken).
    
    The symbolic names are somewhat idiosyncratic and have been crafted by hand.
    '''
    L = sp.root(2, 2 * sp.pi)
    s = sp.sqrt(2 / L**5)
    number_fifths = scale_size - number_fourths
    r_5 = L * L * L * s
    r_4 = L * L * s

    output = []

    start = 1
    running_total = 1
    for index in range(number_fifths):
        scale_symbol = ('#' * (running_total // 7)) + str(start)
        x = r_5**index
        output = output + [(scale_symbol, normalize_interval(x, octave))]
        start = (start + 4) % 7
        if start == 0:
            start = 7
        running_total = running_total + 1

    start = 1
    running_total = 1
    for index in range(number_fourths):
        scale_symbol = ('b' * ((running_total + 4) // 7)) + str(start)
        x = (r_4)**index
        output = output + [(scale_symbol, normalize_interval(x, octave))]
        start = (start + 3) % 7
        if start == 0:
            start = 7
        if start == 1:
            start = 8
        running_total = running_total + 1

    output = output + [("8", sp.Integer(octave))]
    output = list(output)
    if sort:
        output = sorted(set(output), key=lambda x: x[1])
    return output
예제 #8
0
 def test_normalize(self):
     self.assertEqual(sp.Rational(3, 2), normalize_interval(3))
     self.assertEqual(sp.Rational(9, 8), normalize_interval(9))
     self.assertEqual(sp.Rational(21, 16), normalize_interval(21))