Consonance or dissonance?

36

2

Given two note names, you are to write a program that determines if the interval formed by these two notes is consonant or dissonant.

Introduction

In Western music, there are only 12 "different" tones. Their names, sorted from lowest to highest, are these: C, C#, D, D#, E, F, F#, G, G#, A, A#, B. The sequence is cyclical, i. e. it continues with another C after the B, infinitely.

The distance between two tones is called an interval. The interval between any two notes that are adjacent in the series above (e. g. C — C# or E — F) is called a semitone. The interval between more distant notes is defined as the number of semitone steps needed to get from the first to the second (while possibly wrapping around the sequence). Some examples: D to E = 2 semitones, C to G = 7 semitones, B to D# = 4 semitones (this wraps around the sequence).1

Now, these intervals are divided into two categories: consonant (pleasantly sounding if you play the two notes at once) and dissonant (not so much).

Let's define the consonant intervals to be: 0, 3, 4, 5, 7, 8 and 9 semitones.

The rest of them is dissonant, namely: 1, 2, 6, 10 and 11 semitones.

The Challenge

Write a "program" (in the usual broad sense of the word: a function is perfectly OK) to do the following:

  • Take two note names (strings from the sequence above) as an input. You may take them however you like (from stdin, as arguments, separated by whatever you want, even feel free to take them as a list of characters (e. g. ["C","#"]). However, you may not assign any other names to the notes (especially you may not number them from 0 to 11 and use the numbers).

  • For you music geeks out there, the notes will be specified without the octave. In this case, it also does not matter in which order the notes come and which is lower and which is higher. Finally, you don't need to handle any names not in the list above. No other enharmonics like E#, no flats, double-alterations and so on.

  • Pick any two different values. Your program must output one of them whenever the interval formed by the two notes in the input is consonant, and the other if they are not. (Could be True and False, but even π and e if you want :))

  • This is a code-golf. The shortest program in bytes in each language wins. Have fun!

Examples and Test Cases

Note 1    Note 2    Output    Interval [semitones]
  C          D     Dissonant   2
  A#         A#    Consonant   0
  G          D     Consonant   7 (wraparound)
  D#         A     Dissonant   6
  F          E     Dissonant   11
  A          C     Consonant   3

I don't add more of them since there aren't any particularly treacherous cases in this.

This is a first challenge of mine, so any constructive criticism is warmly welcome :—). If you find the theory explanation sloppy, feel free to ask questions. Finally, please do not tell me that this is a dupe of this or this. I made sure it's not. (The latter is quite similar but more complex. I thought that putting up a little simpler challenge will make it easier for people to join.)


1: I tried to simplify this explanation as far as I could. There's a lot more theory around intervals. Please don't bash me for leaving it out.

Ramillies

Posted 2017-09-18T21:10:08.837

Reputation: 1 923

Answers

12

Jelly, 21 bytes

Takes input as a list of two strings. Returns 0 for dissonant or 1 for consonant.

OḢ6×%21_Lµ€IA“¬ɠṘ’æ»Ḃ

Try it online!

OḢ6×%21_Lµ€IA“¬ɠṘ’æ»Ḃ   - main link
         µ€             - for each note             e.g. ["A#", "C"]
O                       -   convert to ASCII codes  -->  [[65, 35], 67]
 Ḣ                      -   keep the first element  -->  [65, 67]
  6×                    -   multiply by 6           -->  [390, 402]
    %21                 -   modulo 21               -->  [12, 3]
       _L               -   subtract the length     -->  [12, 3] - [2, 1] = [10, 2]
           IA           - absolute difference       -->  8
             “¬ɠṘ’      - the integer 540205
                  æ»    - right-shift               -->  540205 >> 8 = 2110
                    Ḃ   - isolate the LSB           -->  2110 & 1 = 0

Making-of

We first should note that the function F that we're looking for is commutative: for any pair of notes (A, B), we have F(A, B) = F(B, A).

Since there are not too many possible inputs and only 2 possible outputs to deal with, it must be possible to find a rather simple hash function H, such that |H(A) - H(B)| produces a limited range of values and is collision-free for all possible pairs of notes (A, B) with respect to the expected output.

We're going to test the set of functions H(mul, mod), which are defined as:

H(mul, mod)(s) = ((ORD(s[0]) * mul) MOD mod) - LEN(s)

Where ORD(s[0]) is the ASCII code of the first character of the note and LEN(s) is the length of the note (2 if there's a '#' and 1 if not).

Below is a commented version of the JS code that was used to find a couple of valid pairs (mul, mod) and the resulting bitmasks. There are many possible solutions, but * 6 % 21 is the shortest one with this method.

// all 12 notes
note = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'];

// reference implementation that takes 2 notes
// and returns 0 for dissonant or 1 for consonant
F = (a, b) =>
  +!~[1, 2, 6, 10, 11].indexOf((note.indexOf(a) - note.indexOf(b) + 12) % 12)

for(mul = 1; mul < 100; mul++) {
  for(mod = 1; mod < 31; mod++) {
    // hash function
    H = s => s.charCodeAt() * mul % mod - s.length;

    table = {};

    // for all possible pairs of notes (a, b)
    if(note.every(a => note.every(b => {
      // compute the table index
      idx = Math.abs(H(a) - H(b));

      // and the expected result
      res = F(a, b);

      // if the slot is empty, store the correct value
      if(table[idx] === undefined) {
        table[idx] = res;
        return 1;
      }
      // else, make sure that the correct value is already there
      return table[idx] == res;
    }))) {
      // if all tests pass: translate the table into a binary mask
      for(n = 0, msk = 0; n < 31; n++) {
        msk |= (table[n] || 0) << n;
      }
      // and display the result
      console.log('mul ' + mul + ', mod ' + mod + ' --> ' + msk);
    }
  }
}

Arnauld

Posted 2017-09-18T21:10:08.837

Reputation: 111 334

3How do you even come up with these things?.. Are you getting these kind of 'algorithms' by hand or by brute-force? And regardless of the answer of the second question: how?!.. :S "literal integer 540205; right shifted with (ASCII code; multiply by 6; modulo 21; keep first; subtract length...); bitwise-AND 1". Your answers keep impressing me every time.. – Kevin Cruijssen – 2017-09-19T14:15:13.847

@KevinCruijssen I've added the original JS code that was used to find these values. – Arnauld – 2017-09-19T15:37:23.813

Thanks for the added explanation. I'm still as impressed as first, but you gave a clear explanation of how you came up with it. Too bad I can only upvote once. – Kevin Cruijssen – 2017-09-19T20:53:02.357

9

APL (Dyalog), 62 39 bytes

Uses ⎕IO←0; 0 is consonant, 1 is dissonant. Takes list of base note chars as left argument and list of sharps as right argument.

{⎕A[|-/('C D EF G A '⍳⍺)+⍵=⍕#]∊'BCGKL'}

Try it online!

{} anonymous function where is the left argument and is the right argument

⎕A[]∊'BCGKL' is the Alphabet, indexed by the following, a member of the string?

  ⍕# format the root namespace (yields the sharp character)

  ⍵= are the right argument chars (the sharps) equal to that?

  ()+ add the following:

   'C D EF G A '⍳⍺ indices of the left argument chars in the string

  -/ difference between those

  | absolute value

Uriel

Posted 2017-09-18T21:10:08.837

Reputation: 11 708

Would you mind adding an explanation for those of us who are unfamiliar with APL? – Draconis – 2017-09-18T23:17:19.287

@Draconis Explanation added. – Adám – 2017-09-18T23:47:05.843

9

MATL, 30 27 26 bytes

,j'DJEFPGIALBC'&mQs]ZP7Mdm

Inputs the two notes in different lines. Outputs 0 for consonant, 1 for dissonant.

Try it online! Or verify all test cases.

Explanation

The 11-character string

DJEFPGIALBC

encodes both the notes and the dissonant intervals, as follows.

The program first finds the 1-based indices of the input characters in the above string. A non-sharp input like D will give 1, E will give 3, ..., C will give 11. These numbers can also be considered 1×1 numeric arrays. A sharp input like C# will give the 1×2 array [11 0], meaning that C was found at position 11 and # was not found.

Observe that letters JPIL will never be present in the input. For now they are only used as placeholders, so that for example note E is two semitones above D. But they will also be useful to define dissonant intervals.

The numbers in the first entry of the 1×1 or 1×2 array correspond to note pitch in semitones, not counting sharp symbols (yet). Observe that the scale defined by these numbers doesn't start at C; but that doesn't matter because we only want intervals, that is, differences between notes. Subtracting the obtained numbers would give either the interval or 12 minus the interval. But first we need to consider the sharp symbol.

To consider sharp notes, a golfy way (in MATL) is to add 1 to each entry of the 1×1 or 1×2 array obtained previously and then sum the array (2 bytes). Thus non-sharp notes are increased by 1 and sharp notes by 2. This makes sharp notes 1 semitone higher than non-sharp notes, as required. We are also adding an extra semitone to all notes, but that doesn't change the intervals between them. So now note D will give pitch number 2, D# will give 3, ..., C will give 12, C# will give 13.

Dissonant intervals are 1, 2, 6, 10, or 11. These have a modulo-12 symmetry: an interval between two notes is dissonant if and only if the interval with the notes in reverse order, modulo 12, is dissonant.

If we compute the consecutive differences of the string 'DJEFPGIALBC' we get the numeric vector

6 -5 1 10 -9 2 -8 11 -10 1

which contains precisely the dissonant intervals, in addition to some negative values, which will be neither useful nor harmful. Observe that it is the choice of additional letters JPIL in the string 'DJEFPGIALBC' that defines (via consecutive differences) the dissonant intervals.

To see if the two input notes are dissonant we take the absolute difference of their pitch numbers. For example, C and D# will give numbers 12 and 3 respectively, and the absolute difference is 9. The actual difference would be -9, and the actual interval would be 3 (obtained as -9 modulo 12). But thanks to the symmetry referred to above, we can consider 9 instead of 3. Since 9 is not present in the vector of consecutive differences, the notes are consonant.

Luis Mendo

Posted 2017-09-18T21:10:08.837

Reputation: 87 464

2I like the way how you encoded both the notes and the dissonant intervals in the same string. – celtschk – 2017-09-19T09:32:02.480

8

JavaScript (ES6), 68 64 bytes

Takes the notes as two strings in currying syntax (a)(b). Returns 0 for dissonant or 1 for consonant.

a=>b=>488055>>(g=s=>'C D EF G A'.search(s[0])-!s[1])(a)-g(b)+9&1

Test cases

let f =

a=>b=>488055>>(g=s=>'C D EF G A'.search(s[0])-!s[1])(a)-g(b)+9&1

console.log(f('C' )('D' )) // 0 (Dissonant)
console.log(f('A#')('A#')) // 1 (Consonant)
console.log(f('G' )('D' )) // 1 (Consonant)
console.log(f('D#')('A' )) // 0 (Dissonant)
console.log(f('F' )('E' )) // 0 (Dissonant)
console.log(f('A' )('C' )) // 1 (Consonant)

Formatted and commented

a => b =>                       // given the two notes 'a' and 'b'
  488055 >>                     // 19-bit lookup bitmask: 1110111001001110111
    (g = s =>                   // we use g() to convert a note 's' into a semitone index
      'C D EF G A'.search(s[0]) // position of the note: -1 for 'B' (not found) to 9 for 'A'
      - !s[1]                   // subtract 1 semitone if the '#' is not there
    )(a)                        // compute the result for 'a'  --> [ -2 ...  9]
    - g(b)                      // subtract the result for 'b' --> [-11 ... 11]
    + 9                         // add 9                       --> [ -2 ... 20]
  & 1                           // test the bitmask at this position (0 if negative or > 18)

Arnauld

Posted 2017-09-18T21:10:08.837

Reputation: 111 334

7

Jelly, 26 bytes

i@€ØAo.SḤ’d5ḅ4µ€ạ/“¢£©½¿‘ċ

A monadic link taking a list of the two notes (as lists of characters) and returning 0 for consonant and 1 for dissonant.

Try it online! or see all inputs in the test-suite.

How?

i@€ØAo.SḤ’d5ḅ4µ€ạ/“¢£©½¿‘ċ - Link: list of lists of characters, notes
              µ€           - for €ach note in notes: (call the resulting list x)
   ØA                      -   yield the uppercase alphabet
i@€                        -   first index of c in ^ for €ach character, c
                           -     ...note '#' is not there so yields 0 (A->1, B->2,...)
      .                    -   literal one half
     o                     -   or (vectorised)  - e.g. "C#" -> [3, 0] -> [3, 0.5]
       S                   -   sum
        Ḥ                  -   double - that is ...  C C#  D D#  E  F F#  G G#  A A#  B
                                                 ->  6  7  8  9 10 12 13 14 15  2  3  4
         ’                 -   decrement         ->  5  6  7  8  9 11 12 13 14  1  2  3
           5               -   literal five
          d                -   divmod                (e.g. 9 -> [1,4] or 11 -> [2,1])
             4             -   literal four
            ḅ              -   convert from base     (e.g. [1,4] -> 8 or [2,1] -> 9)
                                                 ->  4  5  6  7  8  9 10 11 12  1  2  3
                 /         - reduce x with:
                ạ          -   absolute difference   (e.g. ["G#", "A"] -> [12, 1] -> 11)
                  “¢£©½¿‘  - code-page indices = [1, 2, 6, 10, 11]
                         ċ - count occurrences (1 if in the list, 0 if not)

Jonathan Allan

Posted 2017-09-18T21:10:08.837

Reputation: 67 804

5

Jelly, 31 bytes

O_65ị“¢[ḋṃ’b⁴¤+L$€Ḣ€ạ/e“cṾ’b12¤

Try it online!

wheeeeee 32 bytes too long

Explanation

O_65ị“¢[ḋṃ’b⁴¤+L$€Ḣ€ạ/e“cṾ’b12¤  Main link
O                                Cast each character to an int using Python `ord`
 _65                             Subtract 65 (A is 0, G is 7)
     “¢[ḋṃ’b⁴¤                   [2, 3, 5, 7, 9, 10, 0]
     “¢[ḋṃ’                      37058720
           b                     Digits in base
            ⁴                    16
    ị                            Index into this list; this creates the gaps for sharps
                 €               For each sublist
              +L$                Add the length to each element (Sharpens sharp notes)
              +                  Add
               L                 Length
                   €             For each sublist
                  Ḣ              Take the first element
                    ạ/           Absolute difference between the two (unoctaved) pitches # It's convenient that every interval's inverse (?) has the same consonance/dissonance
                      e          Is the semitone difference in
                       “cṾ’b12¤  [1, 2, 6, 10, 11]?
                       “cṾ’      25178
                           b     base
                            12   12

HyperNeutrino

Posted 2017-09-18T21:10:08.837

Reputation: 26 575

Hey, that's a great answer! I was wondering if someone makes use of the symmetry, and you did. And I like your method for matching the note names with numbers as well! +1. – Ramillies – 2017-09-18T21:53:13.667

The semitone difference may be symmetrical but you still get duff results - for example "G#", "A" (dissonant) yields a difference of 11 which is not in [1,2,6].

– Jonathan Allan – 2017-09-18T22:26:11.520

@JonathanAllan oh uh well that's embarrassing; I thought that absolute difference fixed that... ._. will fix lol – HyperNeutrino – 2017-09-18T23:43:13.537

1@JonathanAllan fixed for a couple extra bytes (3 IIRC) – HyperNeutrino – 2017-09-18T23:52:24.190

4

Mathematica, 55 bytes

function                                                  arguments        bytes

FreeQ[1|2|6|10|11]@Abs[#-#2&@@Sound`PitchToNumber/@#]&    [{"C","F#"}]     55

Map the internal built-in Sound`PitchToNumber on the input (list of two strings), take the absolute difference, then pattern match for dissonant interval numbers.


Just for fun (non-competing)

Here are some shorter functions that violate the restriction “you may not assign any other names to the notes.” The rudimentary Music` package has predefined note constants (like A4 = 440.) and the function HertzToCents (which can be golfed). Instead of strings, we will use the note constants as arguments, but given in a different format for each function.

FreeQ[1|2|6|10|11]@Abs@@Round[.01HertzToCents@#]&         [{C3,Fsharp3}]   50+9=59
FreeQ[1|2|6|10|11]@Abs@Round[17Log[#2/#]]&                [C3,Fsharp3]     43+9=52
FreeQ[1|2|6|10|11]@Abs@Round[17Log@#]&                    [C3/Fsharp3]     39+9=48

The package import <<Music`; takes 9 bytes.

This function converts a string (like "F#") into a note constant (like Fsharp3):

Symbol[StringReplace[#,"#"->"sharp"]<>"3"]&                                44

To accept intervals larger than an octave, replace Abs[…] with Mod[…,12].


Why are some intervals considered dissonant? An interval is a ratio of two frequencies. If the ratio has a “simple” numerator and denominator, it tends to be more consonant. In 5-limit tuning, ratios can be factored into integer powers of only prime numbers less than or equal to 5. No interval in equal temperament, besides the octave, is a just interval; they are merely close approximations using powers of the 12th root of 2.

Instead of hard-coding which interval numbers are dissonant, we can find a rational approximation of the interval and then determine if its numerator and denominator are “simple” (meaning that the denominator is less than 5 and the ratio does not divide 7).

This table shows each of the steps in that process.

Table[
  Module[{compoundInterval,simpleInterval,rationalApprox,denomLeq5,div7,consonant},
    compoundInterval = Power[2, i/12];
    simpleInterval   = 2^Mod[Log2[compoundInterval], 1];
    rationalApprox   = Rationalize[N@simpleInterval, 1/17];
    denomLeq5        = Denominator[rationalApprox]<=5;
    div7             = Denominator[rationalApprox]>1 && rationalApprox\[Divides]7;
    consonant        = FreeQ[1|2|6|10|11][Mod[i,12]];

    InputForm/@{
      i, simpleInterval, rationalApprox, 
      denomLeq5, div7, denomLeq5 && !div7,
      consonant
    }
  ], {i, 0, 12}
]

i   sInterval  ratio   denomLeq5  div7       den&&!div  | consonant?

0   1          1       True       False      True       | True
1   2^(1/12)   17/16   False      False      False      | False
2   2^(1/6)    9/8     False      False      False      | False
3   2^(1/4)    6/5     True       False      True       | True
4   2^(1/3)    5/4     True       False      True       | True
5   2^(5/12)   4/3     True       False      True       | True
6   Sqrt[2]    7/5     True       True       False      | False
7   2^(7/12)   3/2     True       False      True       | True
8   2^(2/3)    8/5     True       False      True       | True
9   2^(3/4)    5/3     True       False      True       | True
10  2^(5/6)    7/4     True       True       False      | False
11  2^(11/12)  11/6    False      False      False      | False
12  1          1       True       False      True       | True

The rational approximation lies within 1/17 of the interval because that is the largest threshold that distinguishes between all 12 equal tempered intervals. We match rational numbers with the pattern Rational[a_,b_] (or just a_~_~b_) first, and then match integers with only _.

This culminates in the following rather short function that determines if an arbitrary frequency ratio (greater than 1) is consonant or dissonant.

Rationalize[#,1/17]/.{a_~_~b_:>b<=5&&!a∣7,_->True}&       [Fsharp3/C3]     51+9=60

hftf

Posted 2017-09-18T21:10:08.837

Reputation: 436

1Gosh, don't tell me Mathematica has a builtin even for this... :D – Ramillies – 2017-09-19T18:01:22.080

3

Mathematica, 118 bytes

FreeQ[{1,2,6,10,11},Min@Mod[Differences[Min@Position["C|C#|D|D#|E|F|F#|G|G#|A|A#|B"~StringSplit~"|",#]&/@{#,#2}],12]]&


Input form

["A#","D"]

Outputs

True->Consonant  
False->Dissonant   

thanks @JonathanFrech -16 bytes

J42161217

Posted 2017-09-18T21:10:08.837

Reputation: 15 931

Just a remark: you don't need to output strings Consonant and Dissonant. You may output any two values instead of them (0/1,... whatever). That could save some bytes. – Ramillies – 2017-09-18T21:42:11.720

1Can you not omit the If[...,0,1] and define True->Consonant; False->Dissonant? – Jonathan Frech – 2017-09-18T22:00:13.737

yes of course my friend – J42161217 – 2017-09-18T22:01:13.877

{"C","C#","D","D#","E","F","F#","G","G#","A","A#","B"} (54 bytes) is equivalent to "C|C#|D|D#|E|F|F#|G|G#|A|A#|B"~StringSplit~"|" (46 bytes). – Jonathan Frech – 2017-09-18T22:14:57.550

1StringCases["CC#DD#EFF#GG#AA#B",_~~"#"...] – 42 Bytes – celtschk – 2017-09-19T09:12:16.680

1Also, 2 bytes can be saved by replacing {1,2,6,10,11} with 1|2|6|10|11 – celtschk – 2017-09-19T09:22:19.483

Slightly surprised Mathematica doesn't have a built-in for this. – Skyler – 2017-09-20T13:09:46.810

1@Skyler See the answer below. – hftf – 2017-10-10T22:52:58.170

3

Charcoal, 30 bytes

≔B#A#G#FE#D#C槔o∧⌈ς”⁻⌕ζ⮌θ⌕ζ⮌η

Try it online! Link is to verbose version of code. Outputs 1 for consonant, 0 for dissonant. Explanation:

≔B#A#G#FE#D#Cζ                  Store reversed note names in z
                        θ       First input
                       ⮌        Reversed
                     ⌕ζ         Find index in z
                            η   Second input
                           ⮌    Reversed
                         ⌕ζ     Find index in z
                     ⁻          Subtract
               ”o∧⌈ς”           Compressed string 100111011100
              §                 Circularly index
                                Implicitly print

Neil

Posted 2017-09-18T21:10:08.837

Reputation: 95 035

out of curiosity, is there a mnemonic reason the glyph ⌕ζ is used for "find index"? – Jonah – 2017-09-18T23:39:07.360

@Jonah ζ is the variable assigned to earlier. – Neil – 2017-09-18T23:46:30.107

2

J, 68 bytes

[:e.&1 2 6 10 11[:(12| -~/)(<;._1',C,C#,D,D#,E,F,F#,G,G#,A,A#,B')i.]

explanation

A straightforward, not super golfed implementation in J:

  • Input is given as boxed itemized notes (produced using cut), in order.

  • Find their indexes in the range of notes: (<;._1',C,C#,D,D#,E,F,F#,G,G#,A,A#,B')i.]

  • Subtract the first from the second: -~/

  • Take the remainder when divided by 12: 12|

  • Check if it's one of the dissonant notes: e.&1 2 6 10 11

Try it online!

Jonah

Posted 2017-09-18T21:10:08.837

Reputation: 8 729

2

///, 90 88 bytes

/^/"\///^\/\///C^D/##"E/DD"F/E#"G/FD"A/GD"B/AD"#,#/,"B#^B/#"A#/#"A^G#^G^F#/#"F^E^D#^D/#/

Try it online! (all test cases at once)

  • Put the input after the code.
  • Separate the note names with ,B# in each test case.
  • The output is , for consonant, ,# for dissonant.
  • Support for double-alterations (##) or E# in some particular cases. Otherwise the output is , for consonant, #, for dissonant (thanks to modulo 12 symmetry)
  • Can handle multiple test cases at once (if separated reasonably)
  • Lowercase characters are printed exactly.

user202729

Posted 2017-09-18T21:10:08.837

Reputation: 14 620

2

C (gcc), 91 bytes

g(char*s){return (s[1]&1|2**s&15)*4/5;}f(char*x,char*y){return (1952220<<g(x)>>g(y))&2048;}

call: f("A#", "D")

Return value:

  • Consonant: 2048
  • Dissonant: 0

Bonus: The function is case insensitive.

Try it online!

celtschk

Posted 2017-09-18T21:10:08.837

Reputation: 4 650

Are there not two unnecessary spaces in both return (s? – Jonathan Frech – 2017-09-20T07:43:12.540

You may try g(char*s){s=(s[1]&1|2**s&15)*4/5;}f(char*x,char*y){x=1952220<<g(x)>>g(y)&2048;} and nice solution! – Keyu Gan – 2017-09-20T08:08:55.693

1

Python 2, 125 117 83 78 77 bytes

a,b=map("C C#D D#E F F#G G#A A#B".index,input())
print chr(abs(a-b))in""

Where the "" at the end actually contains the characters "\x02\x04\x0c\x14\x16"

Try it Online!

(+3 because I forgot 11 or 22 in the list to begin with)

-8 bytes from Jonathan Frech and switching to Python 2.

-34 bytes with suggestions from Jonathan Frech and using str's index instead of list's.

-4 bytes from inlining i and Neil's reversing the string suggestion (only -2 really, as i forgot () around a generator)

-5 bytes from un-inlining i & changing the input format

-1 bytes from Jonathan Frech with map() and unprintables.

Takes input in one line of stdin in the format:

'C','C#'

True is dissonant, False is consonant.

Old Explanation:

i='C C#D D#E F F#G G#A A#B'.index
a,b=input()
print abs(i(a)-i(b))in[2,4,12,20]

Python str.index returns the lowest (positive) starting index of a matching substring, so "ABACABA".index("A") == 0 and "ABACABA".index("BA") == 1. Because of this, we can put the note names evenly spaced in a string, and as long as (for example) A comes before A#, the shared A won't be a problem.

i='C C#D D#E F F#G G#A A#B'.index

i is now a function that returns the index in 'C C#D D#E F F#G G#A A#B' its argument (a note name) is, which is 2 * (the number of semitones that note is up from C)

a,b=input()

Python 2's input() is (mostly) equivalent to eval(input()) in Python3, so with a valid input of the format 'C#','F' (for example), a='C#' and b='F'

print abs(i(a)-i(b))in[2,4,12,20]

If the disttance between the first note and the second note in the string is not 2, 4, 12, or 20 (since the note names are represented in 2 characters), then the interval is dissonant, print True, else it is consonant, print False.

pizzapants184

Posted 2017-09-18T21:10:08.837

Reputation: 3 174

Since the input format is not strict, you can use eval(input()) (13 bytes) instead of input().split() (15 bytes). – Jonathan Frech – 2017-09-18T23:22:54.603

101 bytes – Jonathan Frech – 2017-09-18T23:28:46.133

98 bytes – Jonathan Frech – 2017-09-18T23:30:33.000

79 bytes – Jonathan Frech – 2017-09-18T23:47:32.543

Also 79 bytes

– Neil – 2017-09-18T23:54:28.863

76 bytes – Jonathan Frech – 2017-09-18T23:57:10.077

@JonathanFrech Thanks again! (Sorry for misspelling your name, also.) – pizzapants184 – 2017-09-19T00:11:26.710

As is, your code will not run. You would have to add two bytes, either [] or () to build a valid list comprehension or generator expression. Also, please credit @Neil for reversing the string. – Jonathan Frech – 2017-09-19T00:14:44.810

May I ask you what is wrong with my 76 byte suggestion? – Jonathan Frech – 2017-09-19T00:48:05.513

Also, those are 153 bytes; 75 of which are unnecessary spaces. – Jonathan Frech – 2017-09-19T00:51:29.327

@JonathanFrech My apologies about that. My original post didn't have 11 (or 22 for the 2-widths) in it, so it failed for 'C','B'. I've updated the post with your solution (with \x11 added). – pizzapants184 – 2017-09-19T01:00:25.453

chr(abs(a-b))+64in"BDLTV" – orlp – 2017-09-19T01:01:44.513

@orlp Your solution, however, takes more bytes. Python 2 allows for these non-printable characters to exist in strings, though it may be impossible to represent them in an answer. (They appear as empty strings: ``) – Jonathan Frech – 2017-09-19T01:08:43.617

@orlp Also, I think you meant chr(abs(a-b)+64)in"BDLTV". – Jonathan Frech – 2017-09-19T01:10:13.243

1You could use Unicode characters () instead of an emtpy string. – Jonathan Frech – 2017-09-19T01:35:59.350

@JonathanFrech "" added – pizzapants184 – 2017-09-19T01:41:21.873

@JonathanFrech I did mean that. And at the time I wrote that comment it was shorter than what was in the answer. I don't really like unicode/unprintable character abuse in Python. – orlp – 2017-09-19T10:38:07.437

1

C (gcc), 115 117 120 bytes

g(char*a){a=*a-65+!!a[1]*(7-*a/70-*a/67);}f(x,y)char*x,*y;{x="(pP$HL<lt<X"[g(x)]*32+"=ZukW-^h1F6"[g(x)]>>g(y)&1;}

Try it online!

Return 1/0 for consonat and dissonat. It is always interesting to do string manipulation with pure C. Take input as f("A#", "C")

Keyu Gan

Posted 2017-09-18T21:10:08.837

Reputation: 2 028

0

PowerShell, 107 bytes

param($a,$b)[math]::abs(($x=-split'C C# D D# E F F# G G# A A# B').indexof($b)-$x.indexof($a))-in1,2,6,10,11

Try it online!

Outputs True for dissonant and False for consonant.

Takes input $a and $b, the two notes, as strings. Performs a -split operation on the scale, which splits on whitespace, to create an array of the notes, stores that into $x. Finds the .indexof $b in that array, subtracts the index of $a, and then takes the absolute value thereof. Checks whether that number is -in the dissonant ranges.

AdmBorkBork

Posted 2017-09-18T21:10:08.837

Reputation: 41 581

0

Python 2, 68 bytes

lambda a,b,f='C C#D D#E F F#G G#A A#B'.find:3142>>(f(a)-f(b))/2%12&1

Try it online!

Outputs: 1 is dissonant, 0 is consonant.

Lynn

Posted 2017-09-18T21:10:08.837

Reputation: 55 648

0

SQL, 582 bytes

SQL Fiddle

I still have some golfing to do on it, but I wanted to get it down here before I end up breaking it completely.

If the input is in a letter format, then putting those letters in a table with values is okay, right?

CREATE TABLE N(N char(2),v int)
Insert Into N values('A',1),('A#',2),('B',3),('C',4),('C#',5),('D',6),('D#',7),('E',8),('F',9),('F#',10),('G',11),('G#',12);
CREATE TABLE D(D char(9),v int) 
Insert Into D values('C',0),('D',1),('D',2),('C',3),('C',4),('C',5),('D',6);
CREATE FUNCTION I(@A char(2),@B char(2))
RETURNS char(9) as
BEGIN
DECLARE @E int=(SELECT v from N where n=@A),@F int=(SELECT v from N where n=@B)
DECLARE @C char(9) = (SELECT case D when 'D' then 'Dissonant' when 'C' then 'Consonant' END from D where v in(abs(@e-@f),12-abs(@e-@f)))
RETURN isnull(@C,'NotANote')
END

phroureo

Posted 2017-09-18T21:10:08.837

Reputation: 183

0

Perl 5, 106 bytes

("C,C#,D,D#,E,F,F#,G,G#,A,A#,B,"x2)=~/$F[0],(.*?)$F[1],/;$r=(1+($1=~y/,//))%12;say(grep/$r/,(0,3..5,7..9))

Try it online!

Returns false for dissonant, true for consonant.

Xcali

Posted 2017-09-18T21:10:08.837

Reputation: 7 671