######################################################################
# DISS.AWK
######################################################################
# Programmed by: Keith Mashinter Date: April, 1994
# Copyright (c) 1994, 1995 Keith Mashinter
#
# Modifications:
# Date: Programmer: Description:
#
#
# This program measures the degree of sensory dissonance for successive
# acoustic moments. It outputs a single **diss spine containing
# numerical values -- where higher values indicate greater amounts of
# sensory dissonance. The input must consist of one or more **spect spines.
# Each data record in the **spect input represents a concurrent set of
# discrete frequencies ("spectrum"). Spectral data consist of sets of
# paired frequency/amplitude values for each pure tone component present.
# **spect data is in the form ; ; etc.
#
# The program implements Kameoka and Kuriyagawa's method for measuring
# sensory dissonance.
#
#
# FUNCTIONS:
#
# log10() - calculates base-10 logarithms
#
#
# VARIABLES:
#
# f1, f2 - frequencies of pure tone components for which dissonance
# is calculated (NOTE: f1 must be < f2)
# p1, p2 - sound pressure levels (in microbars) of pure tone components
# for which dissonance is calculated
# fb - distance (in hertz) above the lower frequency component to the
# freq having the greatest dissonance
# p0 - sound pressure level for 57 dB SPL (2* 10^-1.15 microbars)
# k0, C0 - scale conversion constants from relative dissonance scale
# (RD) to absolute dissonance scale (AD)
# B - a constant consistent with the dissonance sensation, used
# with the power law in psychological significance
# NOTE: Kameoka & Kuriyagawa used k0=1.0 & C0=65 for their calculations.
#
# outline - output line that is assembled by the program
#
# ARRAYS:
#
# spectspine[] - index to which input spines contain **spect data
# subfield[] - contains data for all multiple stops in the current token
# component[] - points to the two parts (freq & SPL) for a multiple stop
# freq[] - frequency of current spectral component
# loud[] - loudness level of current spectral component
#
BEGIN {
# Define various constants.
k0=1.0
C0=65
B=0.25 # Kameoka & Kuriyagawa's "beta" constant.
n_e=0.20
n_h=0.15
n_l=0.32
p0=2*10^-1.15 # The standard dissonance values are based on two equal-
# -loudness tones having a combined loudness of 60 dB,
# i.e. 57 dB SPL for each tone.
TRUE = 1
FALSE = 0
FS = "\t" # set field separators to TAB only for HUMDRUM spines
OFS = "\t"
num_spectspines = 0 # Number of input spines containing **spect data.
}
###############################################################################
#
# MAIN
#
{
num_components = 0
outline = "" # Reset output line to null string.
for ( i=1; i<=NF; i++ ) # Cycle through all input fields looking for
{ # **spect spines.
if ( $i == "**spect" )
{
num_spectspines++
spectspine[i] = TRUE
if ( num_spectspines == 1 )
{
# If this is the first **spect spine found, then it will be replaced by
# the **diss spine, and all other **spect spine data will be combined
# into the single **diss spine.
diss_spine = i
$i = "**diss"
}
else # Replace extra **spect spines with dummy tandem interpretations.
{
$i = "*"
}
}
else if ( $i ~ "^\*[+x^v]$" ) # check for spine redirections
{
print "diss: This tool does not handle spine redirection."
exit 1
}
else if ( spectspine[i] == TRUE && $i ~ "^[0-9.-]+;[0-9.-]+" )
{
# Store the data from this **spect record in the freq[] and loud[] arrays.
count = split($i, subfield, " ")
for ( j=1; j<=count; j++ )
{
if ( split(subfield[j], component, ";") == 2 )
{
num_components++
# Treat negative frequencies as positive frequencies with a shift of phase.
if (component[1] < 0) freq[num_components] = 0 - component[1]
else freq[num_components] = component[1]
loud[num_components] = component[2]
}
} # for ( j
} # else if
} # for ( i
if ( num_components > 0 )
{
# Cycle through all pairs of pure-tone components.
DIt = 0 # Reset cumulative dissonance intensity to zero.
for ( i=1; i= freq[i])
{
f1 = freq[i]
f2 = freq[j]
p1 = loud[i]
p2 = loud[j]
}
else
{
f1 = freq[j]
f2 = freq[i]
p1 = loud[j]
p2 = loud[i]
}
# Ignore masked components (mentioned after eqn. (15) of K&K Part II).
if (abs(p1,p2) > 25) continue
# DETERMINE THE MAXIMALLY DISSONANT FREQUENCY ABOVE F1.
#
# Calculate the most dissonant frequency (fb) on the basis of the
# lowest tone.
#
# Critical bandwidths increase in size with increasing loudness,
# so the following calculation depends on the loudness of f1.
# Note that equation (6) in K&K only applies for pressure levels
# of 17 dB SPL or greater. In order to avoid a maximally dissonant
# frequency less than or equal to f1, we test for the case of p1 <= 17 dB,
# and if true, suspend the dissonance calculation for the current pair
# of components.
if (p1 <= 17) continue
fb = 2.27*( ((p1-57)/40)+1 ) * f1^0.477 # Equation (6).
# DETERMINE THE ABSOLUTE DISSONANCE OF THE F1-F2 DYAD.
#
# In determining the dissonance three mutually exclusive conditions
# should be considered: (1) Dynamic Domain, (2) Static Domain,
# (3) Supra-Octave. The Dynamic domain occurs when the frequency
# difference is in the first half of the "V". The Static domain occurs
# when the frequency different is in the second half of the "V".
# The Supra-octave domain occurs when the frequency difference is
# greater than an octave.
#
# The absolute dissonance of the F1-F2 dyad is stored in the
# variable D2ei.
#
if ( f2 >= 2*f1 || (f2-f1)/f1 <= 0.01 )
{ # Supra-octave domain -- and near unison domain.
# (We avoid calculations near zero frequency difference in order
# to avoid log(0) when (f2-f1)/f1 is <=0.01)
#
# Assign only dissonance arising from ambient noise.
D2ei = k0*C0 # Equation (9).
}
else
{
if ( f2-f1 <= fb )
{ # Dynamic Domain. Note that the case where (f2-f1)/f1 is close to 0
# is taken care of above to avoid log(0). Equation (7).
D2ei = k0*( 100*( 2+log10((f2-f1)/f1) )/( 2+log10(fb/f1) ) + C0 )
}
else if ( f2-f1 > fb )
{ # Static Domain.
D2ei = k0*( 90*( log10((f2-f1)/f1) )/( log10(fb/f1) ) + 10 + C0 )
}
}
# Compute the Absolute Dissonance Intensity (DI) for the F1-F2 dyad.
DI2ei = (D2ei/k0)^(1/B) # Equation (10).
# Compute dissonance intensity of noise DIn = (Dn0/k0)^(1/B) = C0^(1/B)
DIn = C0^(1/B) # Equation (11).
# Subtract noises from dissonance.
DI2ei = DI2ei - DIn
# Real absolute dissonance of dyads.
D2ei = k0*DI2ei^B # Equation (12)
# Account for SPL levels.
# First change the sound pressure levels from dB SPL to microbars, as
# noted in K&K Part I (after equation (8)).
p1 = 10^(p1/20)/5000
p2 = 10^(p2/20)/5000
if ( p1 == p2 )
{
D2i = D2ei * (p1/p0)^n_e # Equation (13)
}
else if ( p1 > p2 )
{
D2i = ( D2ei*(p1/p0)^n_e ) * (p2/p1)^n_h # Equation (14)
}
else # p1 < p2
{
D2i = ( D2ei*(p2/p0)^n_e ) * (p1/p2)^n_l # Equation (15)
}
# Dissonance Intensity.
DI2i = (D2i/k0)^(1/B) # Equation (16).
# Total dissonance intensity is the sum of all DI2i for all combinations
# of the partials plus the noise: DIt = sum(DI2i) + DIn
# where the sum goes from 1 to M=m*(m-1)/2, m is the number of partials
DIt += DI2i
} # for ( j
} # for ( i
# Add in the ambient noise.
DIt += (k0*C0)^(1/B)
# The total absolute dissonance of the complex tone.
Dm = k0*DIt^B
# Replace the first **spect spine data with the **diss data, and put
# HUMDRUM null tokens (periods) in the leftover **spect spines.
for ( i=1; i<=NF; i++ )
{
if ( spectspine[i] )
{
if ( i == diss_spine ) outline = outline Dm OFS
else outline = outline "." OFS
}
else outline = outline $i OFS
}
# Eliminate any trailing tabs before printing.
sub("\t$","",outline)
print outline
} # if ( num_components
else # No dissonance calculation; just pass the line through.
{
print
}
}
function log10(value)
{
return log(value)/log(10)
}
function abs(value1,value2)
{
if (value1 >= value2) return value1 - value2
else return value2 - value1
}