paint-brush
Đánh hợp âm: Nghệ thuật tạo nên âm nhạc sống động đầy ý nghĩatừ tác giả@charnog
331 lượt đọc
331 lượt đọc

Đánh hợp âm: Nghệ thuật tạo nên âm nhạc sống động đầy ý nghĩa

từ tác giả Denis Gonchar10m2023/10/18
Read on Terminal Reader

dài quá đọc không nổi

Kết hợp niềm đam mê âm nhạc với kiến thức chuyên môn về công nghệ, tác phẩm này hướng dẫn người đọc quá trình tạo một trang web album trực quan khác biệt. Các thách thức, lối tắt mã hóa và vai trò của giao thức Open Graph được nêu bật, đồng thời nhấn mạnh sự cân bằng giữa chủ nghĩa hoàn hảo và khả năng thực thi nhanh chóng.
featured image - Đánh hợp âm: Nghệ thuật tạo nên âm nhạc sống động đầy ý nghĩa
Denis Gonchar HackerNoon profile picture
0-item

Bìa album của DALL·E 3


Trong bài viết này, tôi sẽ chia sẻ cách tôi thực hiện dự án trong một ngày cuối tuần để phát hành album của mình ( https://evhaevla.netlify.app/ ). Tôi không phải là một nhạc sĩ hay nhà soạn nhạc được đào tạo bài bản, nhưng đôi khi, những giai điệu lại hiện lên trong đầu tôi. Tôi ghi lại chúng và để máy tính phát chúng.


Năm 2021, tôi ra mắt album của mình mang tên "Mọi người đều vui, mọi người đều cười". Đó là một album đơn giản của một “nhà soạn nhạc” xa lạ – đó là tôi.


Tôi không chỉ đam mê âm nhạc; Tôi cũng là một nhà phát triển, gần đây chủ yếu tập trung vào công việc frontend. Tôi nghĩ, tại sao không kết hợp hai tình yêu này? Vì vậy, tôi bắt đầu thiết kế một trang web để giới thiệu album của mình một cách trực quan.


Bài viết này sẽ không đi sâu vào từng chi tiết kỹ thuật — bài viết này sẽ quá dài và có thể không hấp dẫn mọi người. Thay vào đó, tôi sẽ nêu bật những khái niệm cốt lõi và những trở ngại mà tôi gặp phải. Đối với những người quan tâm, tất cả mã có thể được tìm thấy trên GitHub .

Thị giác

Album của tôi được sáng tác cho piano nên việc đưa ra quyết định trở nên dễ dàng. Hãy tưởng tượng các hình chữ nhật giảm dần trên các phím đàn piano. Bất kỳ ai có thiên hướng âm nhạc đều có thể gặp nhiều video trên YouTube mô tả các nốt nhạc theo cách này. Một hình chữ nhật chạm vào một phím, làm sáng phím đó, cho biết thời điểm chính xác để đánh nốt nhạc.


Tôi không chắc chắn về nguồn gốc của phong cách hình ảnh này, nhưng tìm kiếm nhanh trên Google chủ yếu mang lại ảnh chụp màn hình của Synthesia.

Giao diện người dùng tổng hợp

Trên YouTube, có những người sáng tạo có thể tạo ra những hiệu ứng trực quan ấn tượng. Xem những video như vậy là một điều thú vị, cả từ góc độ thẩm mỹ và âm nhạc. Xem cái này hay cái này .


Chúng ta sẽ cần phải thực hiện những gì?

  1. Phím
  2. Hình chữ nhật
  3. Âm thanh
  4. Hoạt hình


Hãy giải quyết từng điểm và áp dụng tất cả những điều này vào hành động.

Phím

Ban đầu, tôi cho rằng việc triển khai các phím sẽ đặt ra thách thức lớn nhất. Tuy nhiên, một tìm kiếm trực tuyến nhanh chóng đã tiết lộ rất nhiều ví dụ và hướng dẫn về cách thực hiện điều đó. Nhằm mục đích tạo ra một thiết kế có chút sang trọng, tôi đã chọn một ví dụ do Philip Zastrow tạo ra.


Tất cả những gì còn lại tôi phải làm là sao chép các phím nhiều lần và thiết lập một mạng lưới để các nốt lướt qua. Tôi đã sử dụng Vue.js làm khung giao diện người dùng và bên dưới là mã thành phần.

 <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>

Tôi muốn đề cập rằng tôi đã thêm thuộc tính id vào mỗi khóa, điều này sẽ rất cần thiết khi bắt đầu hoạt ảnh của chúng.

Hình chữ nhật

Mặc dù đây có vẻ là phân khúc đơn giản nhất nhưng vẫn ẩn chứa một số thách thức.


  1. Làm thế nào có thể đạt được hiệu quả của các nốt giảm dần?


  2. Có cần duy trì cấu trúc có thể được truy vấn để truy xuất các ghi chú hiện tại không?


  3. Cách tiếp cận tốt nhất để hiển thị kết quả của các truy vấn đó là gì?


Mỗi câu hỏi đưa ra một trở ngại để điều hướng nhằm đạt được hiệu quả mong muốn một cách liền mạch.


Tôi sẽ không nán lại ở từng câu hỏi mà sẽ đi thẳng vào vấn đề. Với vô số thách thức liên quan đến cách tiếp cận năng động, sẽ là khôn ngoan khi chú ý đến Occam's Razor và chọn giải pháp tĩnh.


Đây là cách tôi giải quyết vấn đề đó: Tôi hiển thị đồng thời tất cả 6215 ghi chú trên một khung vẽ mở rộng. Canvas này được đặt trong một thùng chứa được tạo kiểu bằng thuộc tính overflow: hidden . Khi đó, việc đạt được hiệu ứng nốt rơi chỉ đơn giản là tạo hiệu ứng cho scrollTop của vùng chứa này.


Tuy nhiên, vẫn còn một câu hỏi còn sót lại: làm cách nào để có được tọa độ cho mỗi nốt nhạc?


May mắn thay, tôi có một tệp MIDI nơi lưu trữ tất cả các ghi chú này, một sự tiện lợi mang lại khi trở thành nhà soạn nhạc của album. Nó tập trung vào việc hiển thị các ghi chú bằng cách sử dụng dữ liệu được trích xuất từ tệp MIDI.


Vì tệp MIDI ở định dạng nhị phân và tôi không có ý định tự mình phân tích cú pháp nên tôi đã tranh thủ sự trợ giúp của thư viện tệp midi .


Thư viện midi-file có hiệu quả trong việc trích xuất dữ liệu thô từ tệp MIDI, nhưng đối với nhu cầu của tôi thì điều đó là chưa đủ. Tôi mong muốn chuyển đổi dữ liệu này sang định dạng dễ tiếp cận hơn và thân thiện với ứng dụng hơn để tạo điều kiện hiển thị liền mạch trong ứng dụng.


Trong tệp MIDI, tôi đang xử lý không phải các ghi chú theo nghĩa thông thường mà là các sự kiện. Có rất nhiều sự kiện như vậy nhưng tôi chủ yếu tập trung vào hai loại: 'noteOn', được kích hoạt khi nhấn một phím và 'noteOff', khi phím được thả ra.


Cả hai sự kiện 'noteOn' và 'noteOff' đều chỉ định số nốt cụ thể được nhấn hoặc nhả tương ứng. Thời gian, theo nghĩa thông thường, không có trong MIDI. Thay vào đó, chúng ta có "tích tắc". Số lượng tích tắc trên mỗi nhịp được nêu chi tiết trong tiêu đề của tệp MIDI.

Blue - nhịp (độ dài mặc định là 96 tích tắc), sự kiện nốt đỏ


Quả thực, có nhiều điều cần xem xét hơn. Một bản nhạc nhịp độ cũng hiện diện, chứa các sự kiện 'setTempo' không thể thiếu trong quy trình, vì nhịp độ có thể thay đổi trong khi phát lại. Cách tiếp cận ban đầu của tôi liên quan đến việc điều chỉnh tốc độ hoạt ảnh của thuộc tính scrollTop của vùng chứa để phù hợp với nhịp độ.


Tuy nhiên, tôi sớm nhận ra rằng điều này sẽ không mang lại kết quả như mong đợi do lỗi tích lũy quá nhiều. Việc kéo dài thời gian 'tuyến tính' tỏ ra hiệu quả hơn khi tạo hoạt ảnh cho scrollTop .


Ngay cả khi khía cạnh hoạt hình đã được sắp xếp, nhịp độ vẫn cần được kết hợp. Tôi đã giải quyết vấn đề này bằng cách tự điều chỉnh độ dài của hình chữ nhật của ghi chú. Mặc dù không phải là giải pháp tối ưu (tốc độ thao tác sẽ là lý tưởng) nhưng phương pháp này đảm bảo hoạt động trơn tru hơn.


Giải pháp này không hoàn hảo, chủ yếu là do tôi liên kết một sự kiện nhịp độ với một sự kiện ghi chú dựa trên việc chúng có cùng thời gian hay ít hơn. Điều này có nghĩa là nếu một sự kiện nhịp độ khác xảy ra trong khi một nốt vẫn ở trạng thái chơi thì nó sẽ bị bỏ qua.


Điều này có khả năng gây ra lỗi, đặc biệt nếu một nốt rất dài và có sự thay đổi nhịp độ đáng kể xảy ra trong thời gian phát nốt đó. Đó là một sự đánh đổi. Tôi đã chấp nhận sai sót nhỏ này vì tôi đang tập trung vào sự phát triển nhanh chóng.


Có những trường hợp tốc độ được ưu tiên hơn và sẽ thực tế hơn nếu không bị vướng vào từng chi tiết.

Chỉ sự kiện nhịp độ đầu tiên được liên kết với nốt


Vì vậy, chúng tôi được trang bị các thông tin sau:


  1. Số khóa cụ thể
  2. Thời điểm chính xác phím được nhấn
  3. Thời điểm chính xác chìa khóa được phát hành
  4. Cường độ hoặc "tốc độ" phím được nhấn


Với những chi tiết này trong tay, tôi có thể xác định chính xác tọa độ trên canvas cho từng nốt nhạc. Số phím xác định trục X, trong khi điểm bắt đầu nhấn phím là trục Y. Chiều dài của máy ép quyết định chiều cao của hình chữ nhật.


Bằng cách sử dụng phần tử div tiêu chuẩn và đặt vị trí của nó thành 'tuyệt đối', tôi đã đạt được hiệu quả mong muốn thành công.

Các hình chữ nhật bạn quan sát ở đây chỉ là các phần tử `<div>` đơn giản với các kiểu được áp dụng. Chúng được định vị hoàn toàn trên một khung vẽ kéo dài, sau đó được cuộn qua


Âm thanh

Tôi không có ý định tạo một bộ tổng hợp cho piano vì việc đó sẽ tốn rất nhiều thời gian. Thay vào đó, tôi sử dụng tệp OGG hiện có đã được "kết xuất" và chọn The Grandeur của Native Instruments cho thư viện âm thanh.


Cá nhân tôi tin rằng đây là nhạc cụ VST piano tốt nhất hiện có.


Tôi đã nhúng tệp OGG thu được vào thành phần âm thanh tiêu chuẩn. Nhiệm vụ chính của tôi khi đó là đồng bộ hóa âm thanh với hoạt ảnh scrollTop của khung vẽ ghi chú của tôi.

Hoạt hình

Trước khi tôi có thể giải quyết vấn đề đồng bộ hóa, hoạt ảnh phải được thiết lập trước. Hoạt ảnh canvas khá đơn giản - Tôi tạo hiệu ứng cho scrollTop từ giá trị vô hạn xuống 0 bằng cách sử dụng phép nội suy tuyến tính. Thời lượng của hoạt ảnh này khớp với độ dài của album.


Khi một nốt chạm vào một phím, phím đó sẽ sáng lên. Điều này có nghĩa là đối với mỗi nốt đi xuống, tôi cần "kích hoạt" phím tương ứng và khi nốt đó hoàn thành khóa học, hãy tắt nó.


Với tổng số 6215 ghi chú, con số này tương đương với 12.430 hoạt ảnh kích hoạt và hủy kích hoạt ghi chú.


Ngoài ra, tôi mong muốn cung cấp cho người dùng khả năng tua lại âm thanh, cho phép họ điều hướng đến bất kỳ đâu trong album. Để thực hiện một tính năng như vậy, một giải pháp mạnh mẽ là điều cần thiết.


Và khi phải đối mặt với nhu cầu về một giải pháp đáng tin cậy và "chỉ hoạt động", lựa chọn đầu tiên của tôi luôn là GreenSock Animation Platform .


Hãy xem số lượng mã cần thiết để tạo tất cả hoạt ảnh cho mỗi phím. Sử dụng id để tạo hoạt ảnh cho các thành phần không phải là cách tốt nhất cho Ứng dụng Trang Đơn. Tuy nhiên, phương pháp này thực sự tiết kiệm thời gian. Nhớ lại id tôi đã đề cập cho mỗi khóa? Đây là nơi họ phát huy tác dụng.


 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) })


Mã đồng bộ hóa về cơ bản thiết lập kết nối thông qua các sự kiện giữa âm thanh và dòng thời gian toàn cầu 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() })


Chú thích

Ngay khi tôi cảm thấy muốn kết thúc, một ý tưởng hấp dẫn chợt nảy ra trong đầu tôi. Điều gì sẽ xảy ra nếu tôi thêm một điểm nhấn độc đáo vào album? Ban đầu nó không có trong danh sách việc cần làm của tôi, nhưng tôi cảm thấy dự án sẽ không thực sự tỏa sáng nếu không có tính năng này. Vì vậy, tôi cũng đã chọn kết hợp nó.


Mỗi khi đắm mình vào một ca khúc, tôi lại thấy mình suy ngẫm về những ý nghĩa sâu sắc hơn của nó. Thông điệp gì mà nhà soạn nhạc đang cố gắng truyền tải? Ví dụ, hãy xem xét một đoạn trong cuốn "Nightbook" của Ludovico Einaudi. Tiếng đàn piano vang lên ở tai trái, còn dây đàn vang vọng ở tai phải.


Nó tạo ra bầu không khí của một cuộc đối thoại đang diễn ra giữa hai người. Cảm giác như phím đàn đang thăm dò: "Bạn có đồng tình không?" Các dây đáp lại một cách khẳng định. "Đó có phải là câu hỏi không?" Các dây vang vọng lời khẳng định của họ. Trình tự lên đến đỉnh điểm khi cả hai nhạc cụ hội tụ, tượng trưng cho sự hiện thực hóa sự thống nhất và hòa hợp. Đó không phải là một trải nghiệm đầy mê hoặc sao?


Điều bắt buộc phải đề cập rằng đây hoàn toàn là cách giải thích cá nhân của tôi. Một lần, tôi có cơ hội tham dự buổi hòa nhạc Ludovico ở Milan. Sau buổi biểu diễn, tôi đến gặp anh ấy và hỏi liệu anh ấy có thực sự có ý định đưa khái niệm đối thoại vào phân đoạn cụ thể đó hay không.


Câu trả lời của anh ấy thật sáng tỏ: "Tôi chưa bao giờ suy nghĩ theo cách đó, nhưng bạn chắc chắn có trí tưởng tượng sống động."


Rút kinh nghiệm đó, tôi suy nghĩ: nếu tôi lồng ghép phụ đề vào bản nhạc thì sao? Khi các phân đoạn cụ thể phát, bình luận có thể hiện thực hóa trên màn hình, cung cấp thông tin chi tiết hoặc diễn giải về ý định của nhà soạn nhạc.


Tính năng này có thể mang đến cho người nghe sự hiểu biết sâu sắc hơn hoặc một góc nhìn mới mẻ về "tác giả thực sự muốn nói gì?"


Thật may mắn khi tôi đã chọn GSAP làm công cụ hoạt hình của mình. Nó cho phép tôi dễ dàng tích hợp một dòng thời gian khác, được giao nhiệm vụ cụ thể là tạo hoạt ảnh cho bài bình luận. Sự bổ sung này đã hợp lý hóa quy trình và giúp việc thực hiện ý tưởng của tôi suôn sẻ hơn nhiều.


Tôi có xu hướng giới thiệu các nhận xét thông qua đánh dấu HTML. Để đạt được điều này, tôi đã tạo một thành phần giới thiệu hoạt ảnh trong sự kiện 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>


Việc sử dụng thành phần này sẽ như sau.

 <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>

sự thờ phượng

Với tất cả các yếu tố đã sẵn sàng, bước tiếp theo là lưu trữ trang web. Tôi đã chọn Netlify. Bây giờ xin mời các bạn cùng trải nghiệm album và xem phần trình bày cuối cùng.


https://evhaevla.netlify.app


Tôi thực sự hy vọng ngoài kia sẽ có những nhà phát triển yêu thích piano khác, mong muốn giới thiệu album của họ theo cách độc đáo như vậy. Nếu bạn là một trong số họ, đừng ngần ngại phân nhánh dự án.