paint-brush
코드 연주: 음악에 의미를 부여하는 기술~에 의해@charnog
331 판독값
331 판독값

코드 연주: 음악에 의미를 부여하는 기술

~에 의해 Denis Gonchar10m2023/10/18
Read on Terminal Reader

너무 오래; 읽다

음악에 대한 열정과 기술 전문성을 결합한 이 작품은 독자들에게 독특한 비주얼 앨범 웹사이트를 만드는 과정을 안내합니다. 완벽주의와 신속한 실행 사이의 균형을 강조하면서 도전과제, 코딩 지름길, 오픈 그래프 프로토콜의 역할을 강조합니다.
featured image - 코드 연주: 음악에 의미를 부여하는 기술
Denis Gonchar HackerNoon profile picture
0-item

DALL·E 3 앨범 커버


이번 글에서는 제가 주말에 앨범 발매를 위한 프로젝트를 어떻게 진행했는지 공유하겠습니다( https://evhaevla.netlify.app/ ). 저는 숙련된 음악가나 작곡가는 아니지만, 가끔 마음속에 어떤 곡이 떠오를 때가 있습니다. 나는 그것들을 적어두고 컴퓨터가 그것들을 재생하게 했습니다.


2021년에는 'Everyone is Happy, Everything is Laughing'이라는 앨범을 발매했습니다. 낯선 '작곡가'의 심플한 앨범이다. 바로 나다.


나는 단지 음악을 좋아하는 것이 아닙니다. 저도 개발자인데 최근에는 프론트엔드 작업을 주로 하고 있습니다. 나는 이 두 가지 사랑을 결합하면 어떨까라고 생각했습니다. 그래서 저는 제 앨범을 시각적으로 보여줄 수 있는 웹사이트를 디자인하기 시작했습니다.


이 기사에서는 모든 기술적인 세부 사항을 자세히 다루지는 않습니다. 내용이 너무 길어서 모든 사람의 관심을 끌지 못할 수도 있습니다. 대신, 제가 직면한 핵심 개념과 장애물을 강조하겠습니다. 관심 있는 분들은 GitHub 에서 모든 코드를 찾아보실 수 있습니다.

시각적

내 앨범은 피아노를 위해 작곡되었기 때문에 결정이 간단합니다. 피아노 건반 위로 직사각형이 내려오는 것을 상상해 보십시오. 음악적 성향이 있는 사람이라면 YouTube에서 이러한 방식으로 음표를 묘사하는 수많은 동영상을 접했을 것입니다. 직사각형이 건반에 닿아 빛을 비춰 음을 치는 정확한 순간을 나타냅니다.


이 시각적 스타일의 출처는 확실하지 않지만 빠른 Google 검색을 통해 주로 Synthesia의 스크린샷을 얻을 수 있습니다.

합성 UI

YouTube에는 시각적으로 놀라운 효과를 만들어내는 제작자가 있습니다. 이러한 비디오를 보는 것은 미학적 측면과 음악적 측면 모두에서 큰 즐거움입니다. 이것 또는 이것 을 보십시오.


우리는 무엇을 구현해야 합니까?

  1. 열쇠
  2. 직사각형
  3. 오디오
  4. 생기


각 요점을 다루고 이 모든 것을 실행해 보겠습니다.

열쇠

처음에 나는 키를 구현하는 것이 가장 큰 도전이 될 것이라고 생각했습니다. 그러나 빠른 온라인 검색을 통해 이를 수행하는 방법에 대한 수많은 예제와 가이드가 나타났습니다. 우아함이 가미된 디자인을 목표로 Philip Zastrow 가 만든 예시를 선택했습니다.


나에게 남은 일은 키를 여러 번 복제하고 음표가 미끄러질 수 있도록 그리드를 설정하는 것뿐이었습니다. 저는 프론트엔드 프레임워크로 Vue.js를 사용했고, 아래는 컴포넌트 코드입니다.

 <template> <ul style="transform: translate3d(0, 0, 0)"> <li :id="`key_${OFFSET - 1}`" style="display: none"></li> <template v-for="key in keys" :key="key.number"> <li :class="`${key.color} ${key.name}`" :id="`key_${key.number}`"></li> </template> </ul> </template> <script setup lang="ts"> import { ref } from 'vue' import type { Key } from '@/components/types' const OFFSET = 24 const template = [ { color: 'white', name: 'c' // Do }, { color: 'black', name: 'cs' // Do-diez }, { color: 'white', name: 'd' // Re }, /* ... */ ] const keys = ref<Key[]>([]) for (let i = 0; i < 72; i++) { keys.value.push({ ...template[i % 12], number: i + OFFSET }) } </script>

애니메이션을 시작할 때 필수적인 id 속성을 각 키에 추가했다는 점을 언급하고 싶습니다.

직사각형

이는 가장 단순한 부분으로 보일 수 있지만 눈에 잘 띄지 않는 몇 가지 과제가 숨겨져 있습니다.


  1. 내림차순의 효과는 어떻게 얻을 수 있나요?


  2. 현재 노트를 검색하기 위해 쿼리할 수 있는 구조를 유지해야 합니까?


  3. 이러한 쿼리의 결과를 렌더링하는 가장 좋은 방법은 무엇입니까?


각 질문은 원하는 효과를 원활하게 달성하기 위해 탐색하는 데 장애물을 제시합니다.


각 질문에 더 이상 답변하지 않고 곧바로 본론으로 들어가겠습니다. 동적 접근 방식과 관련된 수많은 과제를 고려할 때 Occam의 면도날에 주의를 기울이고 정적 솔루션을 선택하는 것이 현명합니다.


제가 해결한 방법은 다음과 같습니다. 하나의 넓은 캔버스에 6215개의 노트를 모두 동시에 렌더링했습니다. 이 캔버스는 overflow: hidden 속성으로 스타일이 지정된 컨테이너 내에 보관됩니다. 떨어지는 음표 효과를 얻는 것은 이 컨테이너의 scrollTop 에 애니메이션을 적용하기만 하면 됩니다.


그러나 아직 남아 있는 질문이 남아 있습니다. 각 음표의 좌표를 어떻게 얻을 수 있습니까?


다행스럽게도 나는 이 모든 노트가 보관되어 있는 MIDI 파일을 가지고 있는데, 이는 앨범의 작곡가이기 때문에 편리합니다. 이는 MIDI 파일에서 추출된 데이터를 활용하여 노트를 렌더링하는 것으로 요약됩니다.


MIDI 파일이 바이너리 형식이고 직접 분석할 의도가 없었기 때문에 midi 파일 라이브러리의 도움을 받았습니다.


midi-file 라이브러리는 미디 파일에서 원시 데이터를 추출하는 데 효율적이지만 제 요구에는 충분하지 않습니다. 저는 이 데이터를 보다 접근하기 쉽고 애플리케이션 친화적인 형식으로 변환하여 앱 내에서 원활한 렌더링을 촉진하는 것을 목표로 합니다.


MIDI 파일에서 제가 다루고 있는 것은 일반적인 의미의 노트가 아니라 이벤트입니다. 이러한 이벤트는 매우 다양하지만 저는 주로 두 가지 유형, 즉 키를 눌렀을 때 트리거되는 'noteOn'과 키를 놓을 때 발생하는 'noteOff'에 초점을 맞췄습니다.


'noteOn' 및 'noteOff' 이벤트는 각각 누르거나 놓은 특정 음표 번호를 지정합니다. 전통적인 의미에서 MIDI에는 시간이 없습니다. 대신에 "틱"이 있습니다. 비트당 틱 수는 MIDI 파일 헤더에 자세히 설명되어 있습니다.

파란색 - 비트(기본 길이는 96틱), 빨간색 - 노트 이벤트


실제로 고려해야 할 사항이 더 있습니다. 재생 중에 템포가 변경될 수 있다는 점을 고려하여 프로세스에 필수적인 'setTempo' 이벤트가 포함된 템포 트랙도 있습니다. 나의 초기 접근 방식은 템포에 맞춰 컨테이너의 scrollTop 속성의 애니메이션 속도를 조정하는 것이었습니다.


그러나 과도한 오류 누적으로 인해 예상한 결과가 나오지 않는다는 것을 곧 깨달았습니다. '선형' 시간 연장은 scrollTop 애니메이션에 더 효과적인 것으로 입증되었습니다.


애니메이션 측면이 정렬되었음에도 불구하고 템포에는 여전히 통합이 필요했습니다. 나는 노트의 직사각형 길이를 조정하여 이 문제를 해결했습니다. 최적의 솔루션은 아니지만(조작 속도가 이상적임) 이 방법을 사용하면 보다 원활한 작동이 보장됩니다.


이 솔루션은 완벽하지 않습니다. 주로 시간이 같거나 적은지 여부에 따라 템포 이벤트를 노트 이벤트와 연결하기 때문입니다. 즉, 음표가 아직 연주 상태에 있는 동안 다른 템포 이벤트가 발생하면 해당 이벤트는 무시됩니다.


이는 특히 음표가 매우 길고 재생 중에 극적인 템포 변화가 발생하는 경우 잠재적으로 오류를 일으킬 수 있습니다. 그것은 절충안입니다. 나는 빠른 개발에 집중하고 있기 때문에 이 사소한 결함을 받아들였습니다.


속도가 우선하는 경우도 있고, 모든 세부 사항에 얽매이지 않는 것이 더 실용적입니다.

첫 번째 템포 이벤트만 음표와 연결됩니다.


따라서 우리는 다음과 같은 정보를 갖추고 있습니다.


  1. 특정 키 번호
  2. 키를 누르는 정확한 순간
  3. 열쇠를 놓은 정확한 시간
  4. 키를 누르는 강도 또는 "속도"


이러한 세부 정보를 사용하면 캔버스에서 모든 음표의 정확한 좌표를 정확히 찾아낼 수 있습니다. 키 번호는 X축을 결정하고, 키 누르기 시작은 Y축을 결정합니다. 프레스의 길이는 직사각형의 높이를 결정합니다.


표준 div 요소를 사용하고 위치를 '절대'로 설정하여 원하는 효과를 성공적으로 얻었습니다.

여기에서 관찰되는 직사각형은 스타일이 적용된 간단한 `<div>` 요소입니다. 그들은 길쭉한 캔버스에 절대적으로 위치하며 스크롤됩니다.


오디오

시간이 많이 걸리기 때문에 피아노용 신디사이저를 만들 생각은 없었습니다. 대신, 이미 "렌더링"된 기존 OGG 파일을 사용하고 사운드 라이브러리로 Native Instruments의 The Grandeur를 선택했습니다.


개인적으로 저는 이 악기가 현존하는 최고의 피아노 VST 악기라고 믿습니다.


결과 OGG 파일을 표준 오디오 요소에 포함했습니다. 그때 나의 주요 작업은 내 노트 캔버스의 scrollTop 애니메이션과 오디오를 동기화하는 것이었습니다.

생기

동기화를 다루기 전에 먼저 애니메이션을 설정해야 했습니다. 캔버스 애니메이션은 매우 간단합니다. 선형 보간법을 사용하여 무한 값에서 0까지 scrollTop 에 애니메이션을 적용합니다. 이 애니메이션의 길이는 앨범의 길이와 일치합니다.


음표가 키 위로 내려오면 해당 키에 불이 들어옵니다. 즉, 각 음표의 하강에 대해 해당 키를 "활성화"해야 하며 음표가 해당 코스를 완료하면 비활성화해야 합니다.


총 6215개의 노트로 이는 무려 12,430개의 노트 활성화 및 비활성화 애니메이션에 해당합니다.


또한 사용자에게 오디오를 되감아 앨범 내 어디든 탐색할 수 있는 기능을 제공하는 것을 목표로 했습니다. 이러한 기능을 구현하려면 강력한 솔루션이 필수적입니다.


그리고 "작동하는" 신뢰할 수 있는 솔루션이 필요할 때 저는 항상 GreenSock 애니메이션 플랫폼을 선택합니다.


각 키에 대한 모든 애니메이션을 생성하는 데 필요한 코드의 양을 살펴보세요. id 사용하여 구성 요소에 애니메이션을 적용하는 것은 단일 페이지 애플리케이션에 대한 모범 사례가 아닙니다. 그러나 이 방법은 시간을 크게 절약해 줍니다. 각 키에 대해 언급한 id 기억하시나요? 이것이 그들이 작용하는 곳입니다.


 const keysTl = gsap.timeline() notes.value.forEach((note) => { const keySelector = `#note_${note.noteNumber}` keysTl .set(keySelector, KEY_ACTIVE_STATE, note.positionSeconds) .set(keySelector, KEY_INACTIVE_STATE, note.positionSeconds + note.durationSeconds - 0.02) })


동기화 코드는 기본적으로 오디오와 GSAP 글로벌 타임라인 간의 이벤트를 통해 연결을 설정합니다.


 audioRef.value?.addEventListener('timeupdate', () => { const time = audioRef.value?.currentTime ?? 0 globalTl.time(time) }) audioRef.value?.addEventListener('play', () => { globalTl.play() }) audioRef.value?.addEventListener('playing', () => { globalTl.play() }) audioRef.value?.addEventListener('waiting', () => { globalTl.pause() }) audioRef.value?.addEventListener('pause', () => { globalTl.pause() })


캡션

마무리하고 싶은 순간, 흥미로운 아이디어가 떠올랐습니다. 앨범에 독특한 변형을 추가하면 어떨까요? 원래는 내 할 일 목록에 없었지만 이 기능이 없으면 프로젝트가 진정 빛을 발하지 못할 것이라고 느꼈습니다. 그래서 나도 포함시키기로 결정했다.


한 곡에 몰입할 때마다 그 곡의 더 깊은 의미를 되새기게 되는 나 자신을 발견하게 된다. 작곡가는 어떤 메시지를 전달하려고 했나? 예를 들어 Ludovico Einaudi의 "Nightbook" 부분을 생각해 보십시오. 피아노는 왼쪽 귀에서 공명하고, 현악기는 오른쪽 귀에서 울립니다.


두 사람의 대화가 펼쳐지는 분위기를 연출합니다. 마치 피아노 건반이 "동의하십니까?"라고 묻는 것처럼 느껴집니다. 문자열이 긍정적으로 응답합니다. "그게 질문인가요?" 문자열은 그들의 확언을 반영합니다. 이 시퀀스는 두 악기가 수렴하면서 절정에 이르며 화합과 조화의 실현을 상징합니다. 정말 황홀한 경험이 아닌가요?


이는 순전히 제 개인적인 해석임을 밝힙니다. 한번은 밀라노에서 열린 루도비코 콘서트에 참석할 기회가 있었습니다. 공연이 끝난 후 나는 그에게 다가가서 정말로 그 특정 부분에 대화라는 개념을 삽입할 의도가 있었는지 물었습니다.


그의 반응은 깨달음을 주었다. "나는 그런 식으로 생각해 본 적이 없지만 당신은 확실히 생생한 상상력을 가지고 있습니다."


그 경험을 바탕으로 악보에 자막을 추가하면 어떨까 하는 생각을 하게 되었습니다. 특정 부분이 재생되면 해설이 화면에 나타나 작곡가의 의도에 대한 통찰력이나 해석을 제공할 수 있습니다.


이 기능은 청취자에게 "작가가 진정으로 의미한 것이 무엇이었는가?"에 대한 더 깊은 이해나 새로운 관점을 제공할 수 있습니다.


GSAP를 애니메이션 도구로 선택한 것은 행운이었습니다. 이를 통해 특히 해설 애니메이션 작업을 맡은 다른 타임라인을 쉽게 통합할 수 있었습니다. 이 추가로 인해 프로세스가 간소화되었고 내 아이디어의 구현이 훨씬 더 원활해졌습니다.


나는 HTML 마크업을 통해 주석을 소개하고 싶은 마음이 있었습니다. 이를 달성하기 위해 onMounted 이벤트 중에 애니메이션을 소개하는 구성 요소를 제작했습니다.

 <template> <div :class="$style.comment" ref="commentRef"> <slot></slot> </div> </template> <script setup lang="ts"> /* ... */ onMounted(() => { if (!commentRef.value) return props.timeline .fromTo( commentRef.value, { autoAlpha: 0 }, { autoAlpha: 1, duration: 0.5 }, props.time ? parseTime(props.time) : props.delay ? `+=${props.delay}` : '+=1' ) .to( commentRef.value, { autoAlpha: 0, duration: 0.5 }, props.time ? parseTime(props.time) + props.duration : `+=${props.duration}` ) }) </script>


이 구성 요소의 사용은 다음과 같습니다.

 <template> <div> <Comment time="0:01" :duration="5" :timeline="commentsTl"> <h1>A title for a track</h1> </Comment> <Comment :delay="1" :duration="13" :timeline="commentsTl"> I would like to say... </Comment>

이상

모든 요소를 갖춘 후 다음 단계는 사이트를 호스팅하는 것이었습니다. 저는 Netlify를 선택했습니다. 이제 앨범을 직접 체험해보고 최종 프레젠테이션도 감상해 보시기 바랍니다.


https://evhaevla.netlify.app


저는 진심으로 피아노를 사랑하는 다른 개발자들이 자신의 앨범을 이렇게 독특한 방식으로 선보이고 싶어하기를 진심으로 바랍니다. 당신이 그들 중 하나라면 주저하지 말고 프로젝트를 포크하십시오.