10
\$\begingroup\$

Task

You should write a program that when a button is pressed, it outputs a different sound for each key.

The keys that you will use are:

1!2"34$5%6^78*9(0qQwWeErtTyYuiIoOpPasSdDfgGhHjJklLzZxcCvVbBnm

The notes begin at C and each key above adds one semitone.

Lets make it slightly easier for you though. You can choose between 2 similar tasks,

1) Output a different note when a key is pressed that continues until another key is pressed. This program runs indefinitely

2) Take a string as input that only contains the characters above that will output each note for exactly 1 second (0.05 second variation allowed)

Input

  1. A key being pressed
  2. A single string containing only the symbols above

Output

  1. A single musical note that sounds until another key is pressed
  2. A series of notes, each one 1 second long

Notes

Letter | Note
-------+-------
   1   |  C
   !   |  C#
   2   |  D
   "   |  D#
   3   |  E
   4   |  F
   $   |  F#
   5   |  G
   %   |  G#
   6   |  A
   ^   |  A#
   7   |  B
   8   |  C
   *   |  C#
   9   |  D
   (   |  D#
   0   |  E
   q   |  F
   Q   |  F#
   w   |  G
   W   |  G#
   e   |  A
   E   |  A#
   r   |  B
   t   |  C    <-- middle C
   T   |  C#
   y   |  D
   Y   |  D#
   u   |  E
   i   |  F
   I   |  F#
   o   |  G
   O   |  G#
   p   |  A
   P   |  A#
   a   |  B
   s   |  C
   S   |  C#
   d   |  D
   D   |  D#
   f   |  E
   g   |  F
   G   |  F#
   h   |  G
   H   |  G#
   j   |  A
   J   |  A#
   k   |  B
   l   |  C
   L   |  C#
   z   |  D
   Z   |  D#
   x   |  E
   c   |  F
   C   |  F#
   v   |  G
   V   |  G#
   b   |  A
   B   |  A#
   n   |  B
   m   |  C

Rules

  • You may choose between the tasks but please say which one in your answer
  • You are given a ±0.05 second variation in task 2
  • Each symbol along increments the tone by 1 semitone.
  • The individual notes are shown above
  • This is a so shortest code wins

This was originally taken from here with caird coinheringaahing's consent

\$\endgroup\$
  • \$\begingroup\$ To open this up to languages that don't have audio libraries, I recommend a third output format: a WAV file containing the option 2 output, written to stdout. That would allow entries in any language that can print binary data. \$\endgroup\$ – Ray Jun 4 '17 at 20:54
6
\$\begingroup\$

Python 3, 154 140 bytes

from winsound import*
for i in input():Beep(int(65.406*2**('1!2"34$5%6^78*9(0qQwWeErtTyYuiIoOpPasSdDfgGhHjJklLzZxcCvVbBnm'.find(i)/12)),999)

I chose the second task type.

The loop passes through each character and looks for the index of this element in the string. This index is the number of semitone up from Low C ('1'). Calculation of the desired frequency is made by this formula.

P.S. This library works only in Windows.

-13 bytes thanks to ovs.

-1 byte thanks to Jonathan Allan.

\$\endgroup\$
  • 1
    \$\begingroup\$ You should probably mention that this only works on Windows. \$\endgroup\$ – totallyhuman Jun 4 '17 at 21:18
  • 1
    \$\begingroup\$ Golfed to 141 bytes: from winsound import* for i in input():Beep(int(65.406*2**('1!2"34$5%6^78*9(0qQwWeErtTyYuiIoOpPasSdDfgGhHjJklLzZxcCvVbBnm'.find(i)/12)),1000) \$\endgroup\$ – ovs Jun 4 '17 at 21:51
  • \$\begingroup\$ But the question asked for a program.... \$\endgroup\$ – Neil A. Jun 4 '17 at 23:29
  • \$\begingroup\$ The 0.05 tolerance should allow you to use 999. \$\endgroup\$ – Jonathan Allan Jun 5 '17 at 0:24
  • \$\begingroup\$ @NeilA. codegolf.meta.stackexchange.com/a/6912/4163 \$\endgroup\$ – Bob Jun 5 '17 at 1:00
3
\$\begingroup\$

JavaScript (ES6), 247 or 230 bytes

Decided to try out both options.

Saved a few bytes thanks to @darrylyeo for suggesting the with statement.
Thanks to @Кирилл Малышев for pointing out an issue with .search().

Option 1 - Keyboard Input, 252 246 247 bytes

_=>{c=new AudioContext;q=1;with(c.createOscillator())connect(c.destination),I.oninput=_=>(~(i='1!2"34$5%6^78*9(0qQwWeErtTyYuiIoOpPasSdDfgGhHjJklLzZxcCvVbBnm'.indexOf(I.value,I.value=""))&&(frequency.value=65.4*2**(i/12),q&&start(q=0)))}
<input id=I

Relies on the input element receiving the actual letter typed, instead of character codes. Focus must be on the input box.

Option 2 - String Input, 234 229 230 bytes

s=>{c=new AudioContext;with(c.createOscillator())connect(c.destination),[...s].map((c,i)=>frequency.setValueAtTime(65.4*2**('1!2"34$5%6^78*9(0qQwWeErtTyYuiIoOpPasSdDfgGhHjJklLzZxcCvVbBnm'.indexOf(c)/12),i)),start(),stop(s.length)}

Simply takes the string as the function parameter.

Combined Snippet

I suggest turning down your volume being running this, it can be loud. In order to stop playback, the snippet required adding o= inside each with(c.createOscillator()).

f= // keyboard input
_=>{c=new AudioContext;q=1;with(o=c.createOscillator())connect(c.destination),I.oninput=_=>(~(i='1!2"34$5%6^78*9(0qQwWeErtTyYuiIoOpPasSdDfgGhHjJklLzZxcCvVbBnm'.indexOf(I.value,I.value=""))&&(frequency.value=65.4*2**(i/12),q&&start(q=0)))}

g= // string input
s=>{c=new AudioContext;with(o=c.createOscillator())connect(c.destination),[...s].map((c,i)=>frequency.setValueAtTime(65.4*2**('1!2"34$5%6^78*9(0qQwWeErtTyYuiIoOpPasSdDfgGhHjJklLzZxcCvVbBnm'.indexOf(c)/12),i)),start(),stop(s.length)}

swap=_=>{keyboard=!keyboard;A.innerHTML=keyboard?"Keyboard":"String";S.style.display=keyboard?"none":"inline";window.c&&c.close();window.o&&o.stop();keyboard?f():I.oninput=null;I.value="";}
keyboard=0;
swap();
<span id="A"></span> Input:<br>
<input id="I">
<button id="S" onclick="g(I.value)">Run</button><br>
<button onclick="swap()">Swap</button>

\$\endgroup\$
  • \$\begingroup\$ You can put the oscillator object in a with statement to golf out all instances of o., like this: _=>{c=new AudioContext;with(c.createOscillator())connect(c.destination),I.oninput=_=>(~(i='1!2"34$5%6^78*9(0qQwWeErtTyYuiIoOpPasSdDfgGhHjJklLzZxcCvVbBnm'.search(I.value,I.value=""))&&(frequency.value=65.4*2**(i/12),r||(start(),r=1)))} \$\endgroup\$ – darrylyeo Jun 5 '17 at 5:31
  • 1
    \$\begingroup\$ @darrylyeo Thanks, will implement the with statement, but it breaks the last bit (o.r||(o.start(),o.r=1)) since I was using the fact that o.r is undefined up until that point, but now it errors out since r hasn't been declared. Turns out it was shorter to just declare that variable separate from any objects, so I did that. \$\endgroup\$ – Justin Mariner Jun 5 '17 at 10:59
  • \$\begingroup\$ When you enter the symbol '$', a very high frequency is produced. \$\endgroup\$ – Кирилл Малышев Jun 6 '17 at 10:30
  • \$\begingroup\$ @КириллМалышев Good catch, that's cause .search() treats the input as regex. Switched to .indexOf(). \$\endgroup\$ – Justin Mariner Jun 6 '17 at 10:50
1
\$\begingroup\$

AHK, 130 bytes

s=1!2"34$5`%6^78*9(0qQwWeErtTyYuiIoOpPasSdDfgGhHjJklLzZxcCvVbBnm
Loop,Parse,1
SoundBeep,55*2**((InStr(s,A_LoopField,1)-10)/12),999

I chose Option 2.

Explanation:

Storing the search string as the variable s was shorter than escaping that mess directly in the SoundBeep function. I had to escape the percent sign since that's an escape character for variable names but the rest of the string is OK as is.

By default, the variable 1 is the first input parameter. Feeding that into a parsing loop without specifying a delimiter will result in each character in the input string being analyzed individually.

SoundBeep takes in a frequency in Hz and a duration in milliseconds and plays that note for that long.

The fun part was figuring out the right frequencies. Referencing the piano key frequency table from Wikipedia and the formula on the same page, I found the shortest encoding was to use A1 as the reference note because it's frequency is the nice round 55 Hz. Since we want to start at C1, we have to adjust from A1 (the 13th note) to C1 (the 4th note). That, plus the fact that InStr is one-indexed, is why we subtract 10 from the result of the InStr function.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy