댓글 검색 목록

[javascript] 단 1kb의 JavaScript로 피아노를 만든 방법

페이지 정보

작성자 운영자 작성일 20-08-08 10:12 조회 680 댓글 0

전설적인 자바 스크립트 경쟁 JS1k가 끝났다고 생각했지만 JS1024의 형태로 재 탄생했습니다! 작은 자바 스크립트 프로그램을 만들기 위한 이 경쟁은 10 년 동안 계속되어 왔으며 이제 새로운 이름으로 계속 될 것입니다. 주최자와 다른 참가자들에게 감사 드리며, 다른 출품작도 꼭 확인해야 합니다.


https://frankforce.com/?p=7617#pianostory 


이렇게 적은 양의 코드로 멋지게 만드는 방법을 알아내는 것은 상당히 어려운 일이지만, 이것이 저의 첫 번째 로데오가 아닙니다. 올해에는 두 개의 프로그램을 제출 했으므로 절차 수준의 논리 퍼즐 게임 인 다른 항목 인 Digit Dilemma Plus를 확인하십시오. 콘테스트는 캔버스와 WebGL 프로그램을 위한 "shims", html 스타터 코드를 제공하지만, 제 출품작 중 어느 것도 사용하지 않습니다.


JavaScript는 강력하면서도 간단한 오디오 라이브러리인 Web Audio API를 제공합니다. 처음에는 작은 음향 효과 시스템 인 ZzFX를 개발하면서 그것을 탐구했습니다. 한동안 악기를 만들고 싶었습니다. 이 데모는 주로 오실레이터와 게인 노드를 사용하는 ZzFX와 매우 다르게 작동합니다.


이 게시물에서는 1 킬로바이트 피아노의 모든 라인을 살펴보고 까다로운 부분이 어떻게 작동하는지 설명합니다. 모든 것은 GitHub의 오픈 소스이며 JS1024 제출 이후 몇 가지 개선 사항이 있습니다. 계속 읽어 주시고 함께 달콤한 음악을 만들어요!


여기에 코드 펜에서 라이브로 실행되는 1Key가 있습니다. 마우스 또는 키보드로 재생할 수 있습니다.


https://codepen.io/KilledByAPixel/pen/NWxZmob 


https://github.com/KilledByAPixel/1Keys


https://js1024.fun/demos/2020#16


초기화 


이 프로그램은 거의 전적으로 JavaScript이지만 설정하는 데 필요한 약간의 HTML이 있습니다. 일반적으로 본문은 "document.body"를 사용하여 액세스하지만 공간을 절약하기 위해 본문 ID를 B로 설정합니다. 본문 스타일은 JavaScript 코드에 설정되므로 다른 모든 항목과 함께 압축 할 수 있습니다. 최종 버전에서 모든 코드는 스크립트 태그를 사용하여 몇 바이트를 더 짜내는 대신 본문의 온로드로 이동합니다.


모든 변수와 함수는 단일 문자이지만 몇 개만 있습니다. 구성을 돕기 위해 전역 변수는 대문자를 사용하고 지역 변수는 소문자입니다. 이것은 일반적인 코드 작성 스타일은 아니지만, 매우 작은 프로그램에 사용하는 일종의 속기입니다.


오디오 컨텍스트는 모든 오디오 통화에 사용되는 C로 생성 및 저장됩니다. D 함수는 주어진 인덱스에 대한 피아노 키 html 요소를 가져옵니다. 각 키는 K + i의 고유 한 ID로 생성되므로 이러한 방식으로 액세스 할 수 있습니다. document.getElementById()를 호출하는 대신 eval을 사용하여 "K3"과 같은 ID를 변수 K3으로 변환 할 수 있습니다.


<body id=B><script>

// body style
B.style = `background:#112;color:#fff;user-select:none;text-align:center`;

C = new AudioContext;  // audio context
D = i=> eval(`K` + i); // get piano key div

악기 라디오 버튼 생성 


이 프로그램의 모든 HTML 컨텐츠는 공간을 절약하기 위해 동적으로 작성됩니다. 먼저 네 가지 악기에 대한 라디오 버튼을 추가합니다. 나는 이모티콘이 단어보다 작기 때문에 사용했습니다!


버튼을 만들기 위해 이모지 문자열을 배열로 변환하고 맵을 사용하여 아이콘을 반복하고 각 아이콘에 라디오 버튼을 추가합니다. 입력의 onmousedown 이벤트는 기기가 변경 될 때 설정을 처리합니다.


모든 악기 버튼은 체크로 설정되어 있지만 라디오 버튼이기 때문에 마지막 버튼만 체크 되어 기본 악기입니다. 공간을 절약하는 또 다른 방법입니다.


// instrument select
[...`∿???`].map((i,j)=> // instrument icons
 B.innerHTML += i +        // icon
  `<input type=radio name=I checked onmousedown=I=${ // radio
 3 - j}> `);
);

피아노 키 Div 만들기 


악기 버튼과 마찬가지로 키보드 자체도 동적으로 생성되며 주로 div로 구성됩니다. 피아노를 구성하는 12 개의 건반이 3 줄로 있습니다. 공간을 절약하기 위해 악기 I 및 활성 사운드 어레이 A도 여기서 초기화됩니다.


공식 24 + i % 12 – (i / 12 | 0) * 12는 키 순서 변경을 처리하여 아래쪽 키가 맨 아래에 있도록 합니다. 결과는 변수 k에 저장되어 여러 번 사용됩니다.


피아노 키의 CSS 코드는 많은 공간을 차지합니다. with 및 높이 설정이 작동하려면 디스플레이를 인라인 블록으로 설정해야 합니다. 여백 설정을 사용하여 키를 분리하지만 "outline : 3px solid # 000"과 같은 윤곽선으로 좀 더 보기 좋습니다.


// piano keys
for( I = i = 0; i < 36; A = [])      // 36 keys & init 
 `B.innerHTML += ${i%12 ? `` : `<br>` // new row
 }<div id=K${                        // create key
 k = 24 + i%12 - (i/12|0)*12         // reorder
 } style=display:inline-block;margin:2;background:${ // style

키가 검은 색인지 흰색인지 확인하기 위해 검은 색 키에 해당하는 목록에서 키 인덱스를 조회합니다. 검은 색 키는 margin-left 설정을 사용하여 올바른 위치로 이동합니다.


 `02579`.indexOf(i++%12 - 1) < 0 ?       // b or w
 `#fff;color:#000;width:60;height:180` : // w
 `#000;position:absolute;margin-left:-17;width:33;height:99` // b

마우스 입력을 허용하기 위해 연주 함수 P() 또는 취소 함수 X()를 트리거 하는 각 피아노 키에 대한 마우스 이벤트를 연결합니다. 이러한 콜백 각각은 버튼이 onmouseover에서 눌려 있는지 추가 확인을 제외하고는 매우 유사합니다.


본문의 innerHTML에 추가하므로 div가 자동으로 닫히므로 닫는 태그를 추가 할 필요가 없습니다.


 } onmouseover=event.buttons&&P(${ k // mouse over
 }) onmousedown=P(${ k               // mouse down
 }) onmouseup=X(${ k                 // mouse up
 }) onmouseout=X(${ k                // mouse out
 })>`,                               // end key div

플레이 노트 


플레이 노트 기능은 이 프로그램의 가장 큰 부분이므로 몇 부분으로 나눴습니다. 먼저 아직 재생되지 않은 유효한 음인지 확인합니다. 키를 누르고 있는 동안 onkeydown down이 반복적으로 트리거되므로 후속 통화에서 음표가 재생되지 않도록 해야 합니다.


각 기기는 일련의 숫자로 정의 된 기본 주파수의 배수가 있는 고조파 사인파로 구성됩니다. 이 간단한 시스템으로 우리는 다양하고 흥미로운 사운드를 생성 할 수 있습니다. 예를 들어 오르간은 근음이고 그 위의 3 옥타브는 더 완전한 소리를 냅니다. 다른 악기들도 같은 방식으로 작동합니다. 숫자를 가지고 연주 해보고 어떤 종류의 소리를 만들 수 있는지 확인하십시오.


겹치는 파형을 생성하기 위해 계측기 주파수 승수 문자열을 배열로 변환하고 활성 계측기 I를 반복합니다.


// play note
P = i=> i < 0 || A[i] || // is valid and note not playing 
( 
 A[i] = [        // instruments 
  [...`1248`],   // ? organ 
  [...`3579`],   // ? brass 
  [...`321`],    // ? strings 
  [...`3`],      // ∿ sine 
 ][ I ].map(j=>( // for each harmonic

waves.jpg 

위에서 아래로 웨이브 모양 : 피아노, 색소폰, 현악기 및 웨이브.


마지막으로 오디오 코드입니다. 여기에서 사인파 발진기를 만들고 게인이라고도 하는 볼륨을 설정합니다.


createOscillator와 같은 이러한 함수 중 일부는 매개 변수를 사용하지 않으므로 공간을 절약하기 위해 다른 관련 없는 항목이 삽입됩니다.이 기술은 그렇지 않으면 필요한 추가의 필요성을 줄입니다.


키에 해당하는 div를 얻으려면 앞서 정의한 D 함수를 사용하면 됩니다. 여기에 전환을 재설정 하는 트릭이 있습니다. D (i) .style.transition을 설정 해제해야 하지만 그것만으로는 불가능합니다. "DOM 리플 로우"를 트리거 하고 css 전환을 재설정 하는 요소의 특정 변수 중 하나에 액세스 해야 합니다. 이 경우 innerHTML을 사용하고 있습니다.


반음 오프셋을 오실레이터의 주파수로 변환하는 간단한 공식이 있습니다. 이 방정식은 A1 또는 55hz의 근을 갖는 등분 율 척도를 사용합니다. 루트 주파수에 각 악기의 고조파를 곱하여 겹치는 사운드를 만듭니다. 우리 시스템 음악이 수학을 기반으로 만들어 졌다는 사실은 항상 저에게 놀랍습니다!


오실레이터 및 게인 연결 호출은 연결 함수가 이러한 유형의 연결을 허용하기 위해 전달 된 값을 반환하기 때문에 함께 연결됩니다.

  o = C.createOscillator(      // create oscillator
   D(i).style.transition = 
    D(i).innerHTML),           // reset transition
   o.connect(                  // oscillator to gain
    o.g = C.createGain(        // create gain node
     o.frequency.value =       // set frequency
      j * 55 * 2**((i+3)/12))) // A 55 root note
   .connect(C.destination),    // gain to destination

악기의 각 고조파에는 낮은 음이 더 적은 에너지를 전달하여 더 조용하고 높은 음으로 들리도록 한다는 사실의 균형을 맞추기 위해 음량이 조정되어 있습니다. 또한 클리핑을 방지하기 위해 최대 4 개의 겹치는 사운드가 있으므로 볼륨을 .2로 조정하여 결합 된 진폭이 1을 초과하지 않도록 제한합니다.


모든 설정이 완료되면 start를 호출하여 사운드를 재생할 수 있습니다. 마지막 부분은 A [i]에 저장된 오실레이터 배열을 생성하는 map 함수의 결과로 o를 반환합니다.


  o.g.gain.value = .2/(1+Math.log2(j)), // set gain
  o.start(),                            // start audio
  o)                                    // return sound

음표가 연주되고 있다는 시각적 피드백을 제공하기 위해 키 색상이 빨간색으로 설정됩니다. 하지만 먼저 b라는 데이터 멤버에 원래 색상을 저장해야 해제 후 원래 색상으로 다시 설정할 수 있습니다.


  D(i).b = D(i).style.background, // save original color
  D(i).style.background = `#f00`  // set key color red
);

참고 취소 


즉시 stop을 호출 할 수 있지만 그럴 경우 팝이 발생합니다. 이런 일이 발생하지 않도록 linearRampToValueAtTime을 사용하여 사운드를 줄일 수 있습니다.


image-1.png 

메모를 줄이면서 즉시 중지하는 것입니다.


각 음표는 A [i]를 반복하여 중지해야 하는 여러 오실레이터로 구성됩니다. 키의 배경도 전환 시간이 .5 초인 원래 색상으로 다시 설정되어보다 멋진 프레젠테이션을 위해 점차 애니메이션 됩니다.


실제 중지 이벤트는 setTimeout을 사용하여 350 밀리 초 (.35 초)만큼 지연되어 램프 오프 시간을 처리합니다.


// cancel note
X = i=> A[i] &&                       // is already playing?
 A[i].map(o=>                         // for each oscilator
 setTimeout(i=>o.stop(), 350,         // stop sound after delay
  o.g.gain.linearRampToValueAtTime(   // set gain start ramp
   o.g.gain.value, C.currentTime),    // set gain
    o.g.gain.linearRampToValueAtTime( // ramp off gain
     A[i] = 0, C.currentTime + .3),   // clear note
    D(i).style.transition = `.5s`,    // set transition
    D(i).style.background = D(i).b    // reset original color
 )
);

키보드 제어 


피아노는 마우스 나 키보드로 연주 할 수 있지만 키보드에는 훨씬 더 미묘한 제어를 허용하는 약간 더 많은 코드가 필요합니다. 이 함수 쌍은 onkeydown 및 onkeyup 이벤트에 대한 재생 및 취소 사운드 기능을 트리거 합니다.


키보드 키를 피아노 키에 매핑 하기 위해 키보드와 피아노 키 매핑을 나타내는 문자열을 사용하고 소문자 키를 찾습니다. 이것은 더 작을 수 있지만 이런 방식으로 더 잘 압축되기 때문에 약간의 반복이 있습니다. 대부분의 경우 최적의 압축을 위해서는 작업하기 어려울 수 있지만 정확히 일치하는 코드를 복사하여 붙여 넣는 것이 좋습니다.


// keyboard to piano key mapping
K = `zsxdcvgbhnjm,l.;/q2w3er5t6y7ui9o0p[=]`;

// play note on key down
onkeydown = i=> P(
 K.indexOf(i.key.toLowerCase())               // map key to note
  - 5 * (K.indexOf(i.key.toLowerCase()) > 16) // overlap 2nd row
);

// release note on key up
onkeyup = i=> X(
 K.indexOf(i.key.toLowerCase())               // map key to note
  - 5 * (K.indexOf(i.key.toLowerCase()) > 16) // overlap 2nd row
);

On Blur 


그것은 하나의 작은 버그 수정을 제외한 거의 모든 것입니다. 사용자가 노트를 재생하는 동안 프로그램에서 클릭하면 onkeyup 이벤트가 발생하지 않기 때문에 중단됩니다! 이 문제를 해결하기 위해 onblur 이벤트를 연결하여 모든 활성 노트를 취소 할 수 있습니다.



앞에서 언급했듯이 최종 버전은 onload 이벤트를 사용하지만 닫기 스크립트 태그도 필요합니다.


// stop all sounds if focus lost
onblur = e=> A.map((e,i)=> X(i));

</script>

축소 


코드는 대부분 이미 축소되었지만 1 킬로바이트 장벽을 깨는 데 필요한 몇 가지 트릭이 더 있습니다. 먼저 모든 공백을 제거해야 합니다. 온라인에서 xem의 간결한 것을 사용했습니다. 눈치 채지 못했을 수도 있지만 코드의 모든 문자열은 템플릿 문자열`문자를 사용합니다. 이는 3 가지 문자열 유형을 모두 중첩해야 하기 때문에 중요합니다. 물론 이스케이프 문자를 사용할 수 있지만 약간 더 많은 공간이 필요합니다. 안타깝게도 대부분의 JavaScript minifier는 템플릿 기능을 사용하지 않는 경우`를 "로 대체합니다. 그래서 우리는 모든”를`로 다시 바꿔야 합니다.


이제 JSCrush라는 독창적 인 알고리즘을 사용하여 코드를 압축해야 합니다. 자체 타협하지 않는 JavaScript를 생성하기 위해 zip처럼 약간 작동합니다. 저는 이런 종류의 기능이 뛰어난 siorki의 놀라운 regpack 도구를 사용합니다. 전역 B 본문 변수를 사용하지 않으려면 "변수 이름 다시 할당…… 변수 제외"입력에 B를 추가합니다. 기본 설정으로 코드를 압축하고 여전히 동일하게 작동하는지 확인합니다.


Regpack은 반복되는 문자열을 사용하지 않는 문자로 바꿉니다. 일반적으로 괜찮지 만”은 대체 문자이므로 사용해야 합니다. 따라서”를 Q 또는 기타 사용하지 않는 문자로 바꾸십시오.


”가 필요한 이유는 스크립트 태그가 필요하지 않도록 코드를 <body onload =””>로 래핑 하기 때문입니다. 압축 해제 기 자체에서 작은 따옴표가 필요하므로 따옴표를 대체하는 이 모든 문제를 해결해야 합니다.


최종 결과 1020 바이트 


다음은 최종 코드입니다. 아름답 지 않나요? 글쎄, 모든 사람에게는 아니지만 그들은 아름다움이 보는 사람의 눈에 있다고 말합니다!


<body onload="for(='onWtiW~keyQo.!${ZutYin;marg_^=>FerE;width:Ddown.map((e,o)F.cWnect(.dexOf(;height:;color:#12(Ke.Q.toLowECase())-5*>16)=C.create[…],3tTimestylealue$(e)..transi~=),A[e] =eF,C.curren(Zk})!g.ga..nEHTML Wmouse,l_earRampToVA(backgroundfor(B.=:#1fff;usE-select:nWe;text-align:centE,C=new AudioCWtext,$=iFeval(K+i∿???]B+=e+<_pY type=radio name=I checked=I=Z3-o}> I=i=0;i<36;A=[])B+=Zi%?`:
}<div id=KZk=24+i%-*(i/|0)} =display:_l_e-block^:2;:Z02579i++%-1)<0?#fff000D60180:#000;posi~:absolYe^-left:-17D3399}ovE=event.bYtWs&&P=Pup=XoY=X>,Pe<0|| ||( =[4857921]][I]nF(oOscillator(o!gGa_(!frequency.v=55*n*2**((e+3)/)))C.dest_a~v=.2/(1+Math.log2(n)!start(o).b=,=#f00);X && oFseoY(eF!stop(350v) =0+.3.5s¨C12Czsxdcvgbhnjm,l.;/q2w3E5t6y7ui9o0p[=]`,WQPWQupX)';G=/[-D-F^_YZ!Q~W]/.exec();)with(.split(G))=join(shift());eval(_)"id=B>

이 매우 긴 게시물을 읽어 주셔서 감사합니다. 작은 보너스로 1Keys가 연주하는 짧은 바흐 작품이 있습니다. Rodrigo Siqueira는 이 Bach 데모를 만들었으며 원본 프로토 타입도 작성했습니다.



댓글목록 0

등록된 댓글이 없습니다.

웹학교 로고

온라인 코딩학교

코리아뉴스 2001 - , All right reserved.