突发奇想,使用 TiKZ
绘制了一个音乐播放器, 其实单纯画这样一个播放器比较简单, 于是多做了一些
- 背景图片,颜色支持自定义
- 进度条自动解析
- 歌词解析
- 对当前正在播放的行歌词突出显示
- ...
效果
实现
编写 audio
宏包, audio.sty
latex3
\ProvidesPackage{audio}[xxx]
\ExplSyntaxOn
\tl_new:N \l__audio_name_tl % 歌曲名
\tl_new:N \l__audio_background_picture_tl % 背景图片
\tl_new:N \l__audio_author_tl % 歌手
\tl_new:N \l__audio_words_filename_tl % 歌词
\tl_new:N \l__audio_current_line_style_tl % 歌词突出行样式
\tl_new:N \l__audio_words_style_tl % 歌词样式
\tl_new:N \l__audio_current_time_tl % 当前时间
\tl_new:N \l__audio_total_time_tl % 总时长
\tl_new:N \l__audio_background_color_tl
\tl_new:N \l__audio_avatar_image_tl
\ior_new:N \l__audio_words_ior
\bool_new:N \l__audio_play_bool % 控制是否为播放状态
\dim_new:N \l__audio_progress_line_length_dim % 进度条总长度
\dim_new:N \l__audio_words_line_skip_dim % 歌词行距
\int_new:N \l__audio_current_line_int % 播放当前行
\int_new:N \l__audio_max_show_lines_int % 显示多少行
% 临时变量
\seq_new:N \l__audio_tmpa_seq
\seq_new:N \l__audio_tmpb_seq
\seq_new:N \l__audio_tmpc_seq
\seq_new:N \l__audio_tmpd_seq
\int_new:N \l__audio_tmpa_int
\int_new:N \l__audio_tmpb_int
\int_new:N \l__audio_tmpc_int
\int_new:N \l__audio_tmpd_int
\int_new:N \l__audio_file_line_number_int
\tl_new:N \l__audio_tmpa_tl
\tl_new:N \l__audio_tmpb_tl
\tl_new:N \l__audio_tmpc_tl
\tl_new:N \l__audio_tmpd_tl
\fp_new:N \l__audio_tmpa_fp
\fp_new:N \l__audio_tmpb_fp
\fp_new:N \l__audio_tmpc_fp
\fp_new:N \l__audio_tmpd_fp
\dim_new:N \l__audio_tmpa_dim
\dim_new:N \l__audio_tmpb_dim
\dim_new:N \l__audio_tmpc_dim
\dim_new:N \l__audio_tmpd_dim
\RequirePackage{tikz}
\cs_new:Npn \__audio_pre_icon:n #1 {
\tikz[baseline, #1] {
\draw[line~width = .2ex, color = white] (0, 0) --++ (0, 1ex);
\fill[color = white] (0.1ex, .5ex) -- (1ex, 1ex) -- (1ex, 0) -- cycle;
}
}
\cs_new:Npn \__audio_post_icon:n #1 {
\tikz[baseline, xscale=-1, #1] {
\draw[line~width = .15ex, color = white] (0, 0) --++ (0, 1ex);
\fill[color = white] (0.1ex, .5ex) -- (1ex, 1ex) -- (1ex, 0) -- cycle;
}
}
\cs_new:Npn \__audio_stop_icon:n #1 {
\tikz[baseline, #1] {
\draw[line~width = .3ex, color = white] (0, 0) --++ (0, 1ex);
\draw[line~width = .3ex, color = white] (.5ex, 0) --++ (0, 1ex);
}
}
\cs_new:Npn \__audio_play_icon:n #1 {
\tikz[baseline, #1] {
\fill[color = white] (0, 0) -- (0, 1ex) -- (1ex, 0.5ex) -- cycle;
}
}
\cs_new:Npn \__audio_eval_time:Nn #1#2 {
\exp_args:NNx \seq_set_split:Nnn \l__audio_tmpa_seq {\c_colon_str} {#2}
\fp_set:Nn #1 {
\fp_eval:n{(\seq_item:Nn \l__audio_tmpa_seq{1}) * 60 + \seq_item:Nn \l__audio_tmpa_seq{2}}
}
}
\cs_generate_variant:Nn \__audio_eval_time:Nn {NV}
\cs_new:Npn \__audio_show_progress_line:NNNx #1#2#3#4 {
\group_begin:
\__audio_eval_time:NV \l__audio_tmpa_fp #1
\__audio_eval_time:NV \l__audio_tmpb_fp #2
\fp_set:Nn \l__audio_tmpc_fp { \fp_eval:n { \l__audio_tmpa_fp / \l__audio_tmpb_fp } }
\exp_args:NNV \dim_set:Nn \l__audio_tmpa_dim #3
\dim_set:Nn \l__audio_tmpb_dim {\fp_to_dim:n { \l__audio_tmpc_fp * \l__audio_tmpa_dim }}
\tikz[baseline] {
\draw[white, line~width = .2ex, opacity = .5] (0, 0) -- ++ (\l__audio_tmpa_dim, 0);
\draw[white, line~width = .2ex] (0, 0) -- ++ (\l__audio_tmpb_dim, 0);
\fill[white] (\l__audio_tmpb_dim, 0) circle (4pt);
\node[anchor = south~west, text = white, inner~xsep = 3pt, inner~ysep = 7pt] at (0, 0) {#4};
\node[anchor = south~east, text = white, inner~xsep = 3pt, inner~ysep = 7pt] at (\l__audio_tmpa_dim, 0) {#1/#2};
}
\group_end:
}
\cs_generate_variant:Nn \__audio_show_progress_line:nnnx {VVVx}
\cs_new:Npn \__audio_words_parser:N #1 {
\group_begin:
\exp_args:NNV \ior_open:Nn \l__audio_words_ior #1
% 计算上下方最多显示行数
\int_set:Nn \l__audio_tmpc_int {\int_eval:n {\l__audio_max_show_lines_int - 1} / 2}
\ior_map_inline:Nn \l__audio_words_ior {
\int_incr:N \l__audio_file_line_number_int
\int_compare:nT
{\int_abs:n {\l__audio_file_line_number_int - \l__audio_current_line_int} < \l__audio_tmpc_int}
{
\int_compare:nTF { \l__audio_file_line_number_int = \l__audio_current_line_int }
{
\tl_set:NV \l__audio_tmpa_tl \l__audio_current_line_style_tl
}
{
\tl_set:NV \l__audio_tmpa_tl \l__audio_words_style_tl
}
\__audio_eval_shift_dim:NNN \l__audio_file_line_number_int \l__audio_current_line_int \l__audio_tmpa_dim
\exp_args:NNx \tl_put_right:Nn \l__audio_tmpb_tl
{
\exp_not:N \node[] at ([shift = {(0, \dim_use:N \l__audio_tmpa_dim)}]current~page.center) {\exp_not:o{\l__audio_tmpa_tl} ##1};
}
}
}
\tl_use:N \l__audio_tmpb_tl
\group_end:
}
% #1 current line, #2 line number #3 shift dim
\cs_new:Npn \__audio_eval_shift_dim:NNN #1#2#3 {
% l__audio_words_line_skip_dim
\int_set:Nn \l__audio_tmpa_int {#2 - #1}
\dim_set:Nn #3
{
\fp_to_dim:n { (\l__audio_tmpa_int) * (\l__audio_words_line_skip_dim) }
}
}
\keys_define:nn {audio} {
name.tl_set:N = \l__audio_name_tl,
author.tl_set:N = \l__audio_author_tl,
avatar.tl_set:N = \l__audio_avatar_image_tl,
background~image.tl_set:N = \l__audio_background_picture_tl,
background~color.tl_set:N = \l__audio_background_color_tl,
play.bool_set:N = \l__audio_play_bool,
play.default:n = true,
progress.meta:nn = {audio/progress}{#1},
words.meta:nn = {audio/words}{#1},
}
\keys_define:nn {audio/words} {
line~skip.dim_set:N = \l__audio_words_line_skip_dim,
current~line~number.int_set:N = \l__audio_current_line_int,
source.tl_set:N = \l__audio_words_filename_tl,
style.tl_set:N = \l__audio_words_style_tl,
current~line~style.tl_set:N = \l__audio_current_line_style_tl,
max~lines.tl_set:N = \l__audio_max_show_lines_int
}
\keys_define:nn {audio/progress} {
current~time.tl_set:N = \l__audio_current_time_tl,
total~time.tl_set:N = \l__audio_total_time_tl,
length.dim_set:N = \l__audio_progress_line_length_dim
}
\cs_new:Npn \audio_show:n #1 {
\group_begin:
\keys_set:nn {audio} {#1}
\titlepage
\begin{tikzpicture}[remember~picture, overlay]
\node[inner~sep = 0pt, outer~sep = 0pt, anchor = north~west, opacity = .2] at (current~page.north~west) {\includegraphics[width = \paperwidth]{\tl_use:N \l__audio_background_picture_tl}};
\fill[color = \l__audio_background_color_tl, opacity = .5] (current~page.north~west) rectangle (current~page.south~east);
\node[scale = 1.5, anchor = west, outer~sep = 0pt, ] at ([shift={(3, 3)}]current~page.south~west) {
\__audio_pre_icon:n {scale = 1.5} \quad
\bool_if:NTF \l__audio_play_bool
{
\__audio_stop_icon:n {scale = 1.5}
}
{
\__audio_play_icon:n {scale = 1.5}
}\quad
\__audio_post_icon:n {scale = 1.5}
};
\node[anchor = west, outer~sep = 0pt,] at ([shift={(6, 3)}]current~page.south~west) {
\__audio_show_progress_line:NNNx
\l__audio_current_time_tl
\l__audio_total_time_tl
\l__audio_progress_line_length_dim
{\l__audio_name_tl ( \l__audio_author_tl )}
};
\__audio_words_parser:N \l__audio_words_filename_tl
\clip ([shift = {(4, -4)}]current~page.north~west) circle (2cm);
\node[anchor = north~west, opacity = .5, inner~sep = 0pt] at ([shift = {(2, -2)}]current~page.north~west) {\includegraphics[width = 4cm, height = 4cm]{\tl_use:N \l__audio_avatar_image_tl}};
\end{tikzpicture}
\endtitlepage
\group_end:
}
\NewDocumentCommand{\audio}{}{\audio_show:n}
\NewDocumentCommand{\audiosetup}{m}{\keys_set:nn {audio} {#1}}
\ExplSyntaxOff
\definecolor{bgc}{RGB}{109, 118, 103}
% initial
\audiosetup {
background image = {bgc.jpg},
background color = {bgc},
play,
progress = {
current time = 01:23,
total time = {05:32},
length = {22.7cm}
},
words = {
line skip = {20pt},
source = {words.txt},
style = \color{white}\bfseries\small,
current line style = \color{red}\bfseries\Large,
max lines = 15
}
}
使用
latex
\documentclass[11pt]{ctexart}
\usepackage[paperwidth = 32cm, paperheight = 18cm, margin = 5cm]{geometry}
\usepackage{audio}
\begin{document}
\audio
{
name = {起风了},
author = {买辣椒也用券},
avatar = {avatar},
words = {
current line number = 9
}
}
\audio
{
name = {如果爱忘了},
author = {戚薇},
background image = {bgc1.jpg},
background color = {bgc},
avatar = {avatar1},
play = false,
progress = {
current time = {03:23},
total time = {04:32}
},
words = {
line skip = {24pt},
source = {wordscopy.txt},
style = \color{white}\bfseries\small,
current line style = \color{purple}\bfseries\Large,
current line number = 9,
max lines = 10
}
}
\end{document}