วันศุกร์ที่ 25 พฤศจิกายน พ.ศ. 2554

การเขียนบันทึกข้อความ (หนังสือราชการภายใน) โดยใช้ LaTeX ตอนที่ 3

หลังจากที่สร้างคลาสสำหรับการเขียนบันทึกข้อความไปกว่าปี ก็ได้รับคำถาม/ข้อเสนอแนะ 2 ประการคือ
  1. คลาสเดิมต้องใช้กับการเข้ารหัสแบบ TIS-620 เท่านั้น ดูเหมือนว่าโลกกำลังเคลื่อนตัวเข้าสู่ UTF8
  2. คลาสเดิมทำงานได้กับ latex เท่านั้น ไม่สามารถทำงานกับ pdflatex ได้ เพราะ pdflatex ไม่รองรับแฟ้มภาพในรูปแบบ EPS
จึงทำการปรับปรุงดังนี้

การเข้ารหัส
ในคลาสจะมีข้อความภาษาไทยที่เป็น Hardcode อยู่ เช่นคำว่า "บันทึกข้อความ" "เรื่อง" "เรียน" เป็นต้น ดังนั้นหากต้องการใช้ UTF8 ตั้งแต่ในคลาส ก็ต้องเรียกแพคเกจ inputenc ตั้งแต่ในคลาส โดย

\RequirePackage[thai]{babel}
\RequirePackage[utf8x]{inputenc}

ในส่วนหัวของคลาส และเมื่อเรียกใช้แพคเกจตั้งแต่ในคลาสไปแล้วจึงไม่จำเป็นต้องเรียกแพคเกจ Babel และ inputenc ในแฟ้มเอกสารซ้ำอีก

ภาพตราครุฑ
ภาพตราครุฑต้นฉบับอยู่ที่ wikipedia เป็นภาพแบบ GIF ต้องแปลงแฟ้มให้อยู่ในรูปแบบ PNG (สำหรับ pdflatex) และ EPS (สำหรับ latex) เสียก่อน โชคดีที่ Debain/wheezy มีแพคเกจชื่อ sam2p เอาไว้แปลงภาพได้ดังนี้

> sam2p Tra-Khrut.gif Tra-Khrut.png
> sam2p Tra-Khrut.gif Tra-Khrut.eps

ทีนี้ก็ต้องหาทางบอกคลาสว่าผู้ใช้เรียกใช้คำสั่งอะไรระหว่าง pdflatex กับ latex ซึ่งทำได้โดยเรียกใช้แพคเกจ ifpdf และให้คลาสเลือก

\RequirePackage{ifpdf}
\ifpdf
 \includegraphics[width=20mm]{Tra-Khrut.png}
\else
 \includegraphics[width=20mm]{Tra-Khrut.eps}
\fi

หากไม่ชอบแฟ้ม PNG อาจแปลงแฟ้มภาพเป็น JPG ก็ได้

วันอาทิตย์ที่ 26 มิถุนายน พ.ศ. 2554

การเขียนวิทยานิพนธ์ ม.ข. ด้วย LaTeX - การสร้างหน้าปกและปกใน

การสร้างส่วนปกของวิทยานิพนธ์ แบ่งออกเป็น 3 ส่วนคือ
  1. ปกหน้า ประกอบด้วยตรามหาวิทยาลัย ชื่อวิทยานิพนธ์
  2. ปกใน ภาษาไทยและภาษาอังกฤษ มีรายละเอียดเพิ่มเติมเกี่ยวกับวิทยานิพนธ์
  3. ใบรับรอง ระบุชื่อคณะกรรมการสอบวิทยานิพนธ์ ส่วนให้คณบดีของคณะต้นสังกัด และคณบดีบัณฑิตศึกษาลงนาม
เพื่อดำเนินการในส่วนนี้ได้สะดวก จะต้องมีส่วนที่ศูนย์ คือการกำหนดค่าตัวแปรที่เกี่ยวข้องกับวิทยานิพนธ์ต่าง ๆ เช่นชื่อวิทยานิพนธ์ ชื่อผู้แต่ง หลักสูตร ฯลฯ จะขออธิบายโดยเริ่มจากส่วนที่ศูนย์ก่อน

กำหนดตัวแปรที่สำคัญ
% - Thesis Information
\newcommand*{\titleth}[1]{\def\@titleth{#1}}      % Thesis Title in Thau
\newcommand*{\authorth}[1]{\def\@authorth{#1}}    % Author name with title in Thai
\newcommand*{\authortha}[1]{\def\@authortha{#1}}  % Author name without title in Thai
\newcommand*{\authora}[1]{\def\@authora{#1}}      % Author name with title in English
\newcommand*{\yearth}[1]{\def\@yearth{#1}}        % Academic Year in B.E.
\newcommand*{\degreeth}[1]{\def\@degreeth{#1}}    % Degree in Thai
\newcommand*{\advisorth}[1]{\def\@advisorth{#1}}  % Advisor name in Thai

\newcommand*{\yearen}[1]{\def\@yearen{#1}}        % Academic year in A.D.
\newcommand*{\degree}[1]{\def\@degree{#1}}        % Degree in English
\newcommand*{\advisoren}[1]{\def\@advisoren{#1}}  % Advisor name in English

\newcommand*{\committeechair}[1]{\def\@committeechair{#1}}   % Committee chair name
\newcommand*{\firstcommittee}[1]{\def\@firstcommittee{#1}}   % Committee name
\newcommand*{\secondcommittee}[1]{\def\@secondcommittee{#1}} % Committee name
\newcommand*{\deanofgs}[1]{\def\@deanofgs{#1}}               % Dean of Graduate Schoold
\newcommand*{\deanofen}[1]{\def\@deanofen{#1}}               % Dean of Host Faculty (Need to change to more proper name)

หน้าปก
จะต้องวางตราสัญลักษณ์มหาวิทยาลัยขอนแก่นไว้ตรงกลางหน้า ต่ำจากนั้นลงมาก็จะเป็นชื่อวิทยานิพนธ์ภาษาไทย (ตัวหนา) และภาษาอังกฤษ (ตัวหนาและเป็นตัวใหญ่ทั้งหมด) ตามลำดับ จากนั้นเว้นระยะไป 6 ซ.ม. จึงจะเป็นชื่อผู้แต่งไม่มีคำนำหน้า (ตัวหนา) เว้นระยะไปอีก 6.9 ซ.ม. ก็จะเป็นการประกาศวุฒิ มหาวิทยาลัย และปีการศึกษา ซึ่งส่วนของวุฒิและปีการศึกษานั้นจะรับมาจากตัวแปรข้างบน ส่วนชื่อมหาวิทยาลัยขอนแก่นนั้นจะฝังลงไปในคลาสเลย สำหรับตัวเลขระยะที่ระบุในส่วนนี้ได้จากการวัดระยะตัวอย่างวิทยานิพนธ์จริง ฝังระยะคงที่นี้ไว้ในคลาสเลยเช่นเดียวกัน แต่ว่าน่าจะปรับปรุงได้ในอนาคต
% - Front Cover
\begin{titlepage}
\begin{ċenter}
 \begin{figure}
   \begin{ċenter}
  \IfFileExists{kkuNew.eps}{\includegraphics[scale =0.1]{kkuNew.eps}}{File kkuNew.eps is not found.}
     \end{ċenter}
 \end{figure}
\end{ċenter}
\begin{ċenter}
{\large \textbf{\MakeUppercase{\@titleth}}} \\
{\large \textbf{\MakeUppercase{\@title}}}
\end{ċenter}
\vspace{6cm}
\begin{ċenter}
{\large \textbf{\MakeUppercase{\@authorth}}}
\end{ċenter}
\vspace{6.9cm}
\begin{ċenter}
{\large \textbf{วิทยานิพนธ์ปริญญา\@degreeth \\ มหาวิทยาลัยขอนแก่น} \\
\textbf{พ.ศ. \@yearth}}
\end{ċenter}
\end{titlepage}%

ปกใน
ปกในมีสองส่วนย่อยคือปกในภาษาไทยและปกในภาษาอังกฤษ ซึ่งทั้งสองส่วนจะมีโครงสร้างคล้าย ๆ กันคือ ชื่อวิทยานิพนธ์อยู่กลางหน้าด้านบน (ตัวหนา) เว้นระยะ 9 ซ.ม. จะเป็นชื่อผู้แต่งมีคำนำหน้า (ตัวหนา) และเว้นระยะอีก 9 ซ.ม. ก็จะเป็นการประกาศว่าวิทยานิพนธ์นี้เป็นส่วนหนึ่งของหลักสูตรอะไร สาขาอะไร ในมหาวิทยาลัยขอนแก่น ในส่วนนี้ผมนึกชื่อตัวแปรที่จะใช้บรรจุสาขาวิชาไม่ออก เลยเขียน hard code ลงไปในคลาสเลย ควรได้รับการปรับปรุงเพื่อให้สามารถส่งชื่อสาขาวิชาผ่านตัวแปรได้ ในค่าระยะคงที่ที่ใช้ในคลาสมาจาการวัดจริงจากตัวอย่างวิทยานิพนธ์เช่นเดียวกัน
% Title page in Thai
\begin{titlepage}
\thispagestyle{empty}
\begin{ċenter}
{\large \textbf{\MakeUppercase{\@titleth}}}
\end{ċenter}
\vspace{9cm}
\begin{ċenter}
{\large \textbf{\@authorth}}
\end{ċenter}
\vspace{9cm}
\begin{ċenter}
\textbf{วิทยานิพนธ์นี้เป็นส่วนหนึ่งของการศึกษาตามหลักสูตรปริญญา\@degreeth}\\
\textbf{สาขาวิชาวิศวกรรมไฟฟ้า}\\
\textbf{บัณฑิตวิทยาลัย มหาวิทยาลัยขอนแก่น}\\
\textbf{พ.ศ. \@yearth}
\end{ċenter}
\end{titlepage}

% Title page in English
\begin{titlepage}
\thispagestyle{empty}
\begin{ċenter}
{\large \textbf{\MakeUppercase{\@title}}}
\end{ċenter}
\vspace{9cm}
\begin{ċenter}
{\large \textbf{\MakeUppercase{\@author}}}
\end{ċenter}
\vspace{8.7cm}
\begin{ċenter}
\textbf{\MakeUppercase{A thesis submitted in partial fulfillment of the requirements}}\\
\textbf{\MakeUppercase{for the degree of \@degree}}\\
\textbf{\MakeUppercase{in Electrical Engineering}}\\
\textbf{\MakeUppercase{Graduate School Khon Kaen University}}\\
\textbf{\MakeUppercase{\@yearen}}
\end{ċenter}
\end{titlepage}

ใบรับรอง
ในหน้านี้จะประกอบด้วยตราสัญลักษณ์มหาวิทยาลัยขอนแก่นตรงกลางหน้าด้านบน ต่ำลงมาจะเป็นคำประกาศใบรับรองวิทยานิพนธ์ ตรงจุดนี้ผมใช้การใส่ข้อมูลตรง ๆ ลงไปในคลาส ไม่ผ่านตัวแปรซึ่งควรแก้ไข ต่ำลงมา 1 ซ.ม. จะเป็นชื่อวิทยานิพนธ์ ต่ำลงมาอีก 1 ซ.ม. จะเป็นชื่อผู้เขียน (มีคำนำหน้า) ต่ำลงมาอีก 1 ซ.ม. จะเป็นรายชื่อกรรมการสอบวิทยานิพนธ์ เว้นระยะลงมา 2 ซ.ม. เป็นชื่ออาจารย์ที่ปรึกษา หลังจากนั้นจะเป็นส่วนที่จะให้คณบดีคณะต้นสังกัด และคณบดีบัณฑิตวิทยาลัยลงนาม
% Certificate page
\begin{titlepage}
\thispagestyle{empty}
\begin{ċenter}
 %\vspace{2.5cm}
 \begin{figure}
   \begin{ċenter}
  \IfFileExists{kkuNew.eps}{\includegraphics[scale =0.1]{kkuNew.eps}}{File kkuNew.eps is not found.}
     \end{ċenter}
 \end{figure}
\end{ċenter}
\begin{ċenter}
\textbf{ใบรับรองวิทยานิพนธ์}\\
\textbf{มหาวิทยาลัยขอนแก่น}\\
\textbf{หลักสูตร}\\
\textbf{วิศวกรรมศาสตรมหาบัณฑิต}\\
\textbf{สาขาวิชาวิศวกรรมไฟฟ้า}
\end{ċenter}
\vspace{1cm}
\begin{flushleft}
\textbf{ชื่อวิทยานิพนธ์}: \hspace{2em} \@titleth \\
\vspace{1cm}
\textbf{ชื่อผู้ทำวิทยานิพนธ์}: \hspace{2em} \@authorth \\
\vspace{1cm}
\begin{tabular}{@{}lll}
\textbf{คณะกรรมการสอบวิทยานิพนธ์} & \@committeechair  & ประธานกรรมการ \\
                               & \@firstcommittee & กรรมการ \\
                               & \@secondcommittee  & กรรมการ
\end{tabular}
\end{flushleft}
\vspace{2cm}
\textbf{อาจารย์ที่ปรึกษาวิทยานิพนธ์:}\\
\begin{ċenter}
% \makebox with zero width is free from center environment
% this is very handy for signature line.
% Could be useful for official memo (Thailand) too.
\makebox[6cm][l]{\dotfill{}}\makebox[0pt][l]{ อาจารย์ที่ปรึกษา}\\
(\@advisorth)
\end{ċenter}
\vspace{1cm}
\begin{ċenter}
\begin{tabular}{ccc}
\dotfill{}&&\dotfill{}\\
 (\@deanofgs)&&(\@deanofen)\\
คณบดีบัณฑิตวิทยาลัย && คณบดีคณะวิศวกรรมศาสตร์ \\
\end{tabular}
\end{ċenter}
\vspace{1cm}
\begin{ċenter}
ลิขสิทธิ์ของมหาวิทยาลัยขอนแก่น
\end{ċenter}
\end{titlepage}

นำคำสั่งทุกคำสั่งที่ตั้งแต่ส่วนที่ใช้สร้างปกจนถึงส่วนใบรับรองบรรจุไว้ในส่วนสร้างคำสั่ง \maketitle
\renewcommand\maketitle{
% Commands that create covers and certificate page described earlier
}
หมายความว่าคำสั่งทั้งหมดที่ใช้สร้างปกที่อธิบายไว้ข้างต้น จะต้องอยู่ระหว่าง { และ } ของคำสั่ง \renewcommand\maketitle นั่นเอง

วันเสาร์ที่ 25 มิถุนายน พ.ศ. 2554

การเขียนวิทยานิพนธ์ ม.ข. ด้วย LaTeX - ความเป็นมาและการสร้างคลาส

ความเป็นมา
เรื่องการเขียนวิทยานิพนธ์ ม.ข. ด้วย LaTeX นี่เริ่มเมื่อประมาณปีที่แล้ว คือหลังจากที่ได้ศึกษาเอกสารที่เกี่ยวข้องมาบ้าง (เคยบันทึกไว้ที่นี่) ก็เกิดความอหังการ คิดว่าเราน่าจะทำได้เหมือนกัน จึงคุยกับนักศึกษาปริญญาโทที่อยู่ในความดูแล (คุณอดิเทพ กงจันทร์) ว่ามาช่วยกันทำคลาส LaTeX สำหรับวิทยานิพนธ์กันดีไหม แล้วจะได้ใช้คลาสนี้เขียนวิทยานิพนธ์ส่งเขาไปเลย ไม่ต้องใช้ MS-Word คุณอดิเทพตกลง (เคยคุยกันเล่น ๆ ช่วงใกล้เส้นตายส่งงานว่าคิดถูกหรือคิดผิดก็ไม่รู้ :p)

พวกเราก็เลยร่วมกันพัฒนาคลาสสำหรับวิทยานิพนธ์ ม.ข. นี้ร่วมกันมา โดยเริ่มต้นจากตัวอย่างวิทยานิพนธ์ของ (รอชื่อ - สกุลจริงอยู่) อดีตนักศึกษาระดับบัณฑิตศึกษาของภาควิชาคณิตศาสตร์ คณะวิทยาศาสตร์มหาวิทยาลัยขอนแก่น ซึ่งใช้ LaTeX เขียนวิทยานิพนธ์ภาษาอังกฤษแบบ Hard Code คือไม่ได้สร้างเป็นคลาส และไม่ได้ใช้ภาษาไทย

ก็ช่วยกันทำมาเรื่อย ๆ ตั้งแต่เมื่อราว ๆ เดือนพฤศจิกายน 2553 เป็นต้นมา บัดนี้วิทยานิพนธ์ของคุณอดิเทพก็ส่งไปแล้วโดยใช้คลาสที่ช่วยกันเขียนช่วยกันแก้นี่แหละ แม้ว่าคลาสจะยังไม่สมบูรณ์และยังมีส่วนที่ยังปรับปรุงได้อีกมาก แต่ก็ถือว่าใช้ประโยชน์ได้แล้ว จึงถึงเวลาที่จะนำมาเผยแพร่ให้เป็นประโยชน์และให้ชุมชนช่วยกันพัฒนาต่อไป

ในการนี้ได้รับคำแนะนำจากคุณเทพพิทักษ์ในกิจกรรมโสเหล่ครั้งที่ 12 ของ KKLUG ว่าน่าจะอัพโหลดต้นฉบับไว้ที่โฮสต์ที่ไหนสักที่หนึ่ง เพื่อที่ทุก ๆ คนจะได้ร่วมกันพัฒนาต่อไปได้โดยง่าย จึงได้อัพโหลดต้นฉบับคลาสไว้ที่ sourceforge.net ซึ่งหากผู้อ่านท่านใดสนใจก็ขอให้ดาวน์โหลดต้นฉบับโปรแกรมได้และหากติดปัญหาหรือมีข้อเสนอแนะใด ๆ (น่าจะมีเยอะอยู่เพราะโปรแกรมยังไม่มีตัวอย่างและไม่มีคู่มือ) ก็สามารถติดต่อผู้เขียนได้ที่ห้องทำงานครับ

ในส่วนของบล๊อกก็จะทยอยอธิบายการทำงานของโปรแกรมไปทีละส่วน ๆ เช่นก่อนหน้านี้เคยเขียนไปแล้วหนหนึ่งคือเรื่องการสร้างสารบัญ (เป็นอีกเรื่องหนึ่งที่ได้รับคำแนะนำจากโสเหล่ว่าน่าจะพัฒนาได้อีก)

การสร้างคลาส
เนื่องจากคลาสค่อนข้างใหญ่ มีการกำหนดค่าต่าง ๆ มากมายจึงขอนำมาเสนอทีละส่วน โดยจะนำการสร้างคล้าสจากเล็กไปหาใหญ่ทีละขั้น ๆ เริ่มที่การสร้างส่วนที่จำเป็นสำหรับคลาสเป็นอันดับแรก ส่วนประกอบที่สำคัญของคลาสประกอบด้วย
  • ส่วนหัวที่จำเป็นและการประกาศคลาสที่กำลังสร้างอยู่
  • หากมีคลาสต้นแบบให้ LoadClass ต้นแบบขึ้นมา หากเราไม่เพิ่มหรือแก้ไขการตั้งค่าในส่วนใด ส่วนนั้นก็จะใช้ค่าตั้งจากคลาสต้นแบบ
  • ระบุขนาดกระดาษ
  • ระบุขนาดและแบบอักษร
ซึ่งได้ลงคอมเมนต์ไว้ในโค้ดแล้วว่าส่วนใดเป็นส่วนใดดังนี้
\NeedsTeXFormat{LaTeX2e} % ส่วนหัวที่จำเป็นสำหรับคลาส
\ProvidesPackage{kkuthesis}[2010/11/09 Khon Kaen University Thesis Class] % ชื่อคลาสและรายละเอียด

% ใช้คลาส report เป็นต้นแบบ
\LoadClass[a4paper,oneside,openbib]{report} 

% ใช้ภาษาไทย
\RequirePackage[english,thai]{babel} 
\RequirePackage{thswitch}

% ใช้ตั้งค่าหน้ากระดาษ
\RequirePackage{geometry}

% ใช้เพิ่มรูปภาพ
\RequirePackage{graphicx}

% ตั้งค่าหน้ากระดาษ
\geometry{top=1.5in,bottom=1in,left=1.5in,right=1in,headsep=0.8in}

% ตั้งค่าแบบอักษร อันที่จริงน่าจะใช้ kinnari ได้
% จะใช้แบบอักษรอังศณา จะต้องติดตั้งแบบอักษรอังศณาให้ระบบ LaTeX เสียก่อน 
\def\thairmdefault{angsa}
\usefont{LTH}{angsa}{m}{n}
\fontsize{14pt}{17pt}
\selectfont

เก็บแฟ้มนี้ไว้ในชื่อ kkuthesis.cls เวลาเรียกใช้ก็สามารถเรียกใช้คลาสนี้แทน report ได้ทันที ถึงจุดนี้สิ่งที่เราทำก็มีเพียงการตั้งค่าหน้ากระดาษและแบบอักษรเท่านั้น ในคราวต่อไปจะนำเสนอส่วนที่ใช้สร้างปก ทั้งปกนอกและปกใน ทั้งภาษาไทยและภาษาอังกฤษต่อไป

วันศุกร์ที่ 27 พฤษภาคม พ.ศ. 2554

เครื่องมือช่วยพล๊อตกราฟ

ช่วงหลายปีที่ผ่านมาผมทดลองใช้โปรแกรมสำหรับวิเคราะห์และประมวลสัญญาณแทน Matlab สลับไปสลับมาระหว่าง Octave กับ Scilab จุดด้อยของ 2 โปรแกรมนี้สำหรับคนที่คุ้นเคยกับ Matlab ก็คือการพล๊อตกราฟ กล่าวคือมันไม่ค่อยสะดวกสำหรับคนที่ชินกับระบบพล๊อตของ Matlab

หลังจากสำรวจรายการแพคเกจของ wheezy เกี่ยวกับการพล๊อตแล้วก็พบแพคเกจชื่อ plotdrop ซึ่งมีรายละเอียดน่าสนใจดังนี้ (จากรายละเอียดของแพคเกจ)
PlotDrop is designed for quick simple visualisation of 2D data series.
It is intended to be used in tandem with an external filesystem browser
such as GNOME's nautilus or KDE's konqueror. Files containing data are added
by dragging them from the browser to the file list.
อันที่จริงมีแพคเกจที่เกี่ยวกับ Data Visualization อีกเยอะเลยครับ แต่ตัวนี้มีลูกเล่นน้อยที่สุดทำให้รู้สึกว่าเรียนรู้ได้ง่ายที่สุด ก็เลยเลือกมาลองก่อน เราสามารถติดตั้งได้ทันทีโดย
# sudo apt-get install plotdrop
หากเป็นเครื่องของผมซึ่งเป็น wheezy แพคเกจนี้จะไปอยู่ในเมนูรูปภาพ (Graphic) ครับ เมื่อเรียกโปรแกรมนี้จากเมนูเราจะเห็นหน้าต่างแบบนี้
สิ่งที่เราต้องทำก็คือเอาข้อมูลให้โปรแกรมนี้ไป ข้อมูลจะต้องเป็น
  1. แฟ้มข้อความ เข้ารหัสแบบใดก็น่าจะได้ แต่ที่ผมใช้อยู่และไม่มีปัญหาคือ UTF-8
  2. ข้อมูลแยกหลัก (Column) กล่าวคือ ข้อมูล ก. อยู่หลัก 1 ข้อมูล ข. อยู่หลัก 2 ... อย่างนี้เรื่อยไป
  3. จำนวนแถว (Row) น่าจะต้องเท่ากัน ผมยังไม่เคยลองว่าถ้าจำนวนแถวไม่เท่ากันจะเกิดอะไรขึ้น
เราเรียกการจัดข้อมูลแบบนี้ว่า Delimated File และใน Octave เรามีคำสั่งที่ใช้จัดเตรียมชุดข้อมูลเพื่อพล็อตแบบนี้โดยใช้คำสั่ง dlmwrite ครับ เช่นถ้าเราต้องการพล็อตแกน x เป็นความถี่ในเวคเตอร์ f ขนาด 100x1 และแกน y เป็นความหนาแน่นสเปคตรัมกำลัง (Power Spectrum Density) P ขนาด 100x1 เช่นกัน เราจะใช้คำสั่ง dlmwrite สร้างแฟ้มข้อมูลที่ใช้กับ plotdrop ได้ดังนี้
octave> dlmwrite('mydlmfile.dlm',[f P]," ");
นี้หมายความว่า เราส่งข้อมูล f และ P ไปในเมตริกซ์เดียวกัน ส่งเข้าไปในแฟ้มข้อความธรรมดาชื่อ mydlmfile.dlm โดยที่ระหว่างข้อมูล f และ P จะคั่นด้วย Space (" ") เช่นถ้า
octave> f = [0 10 20 30]';
octave> P = [12 15 10 5]';
หน้าตาของแฟ้มข้อมูล mydlmfile.dlm จะเป็น
0 12
10 15
20 10
30 50
เราสามารถลากแฟ้มนี้ไปวางในพื้นที่ว่างในรูปข้างบนได้เลย หรือจะเรียกใช้คำสั่ง Series => Add File ... ก็ได้ เมื่อเราส่งข้อมูลให้ plotdrop ได้แล้ว เราสามารถ
  • เติมหัวเรื่อง ฉลากแกน x และฉลากแกน y ได้ในแท็ป Caption (แท็ปที่ 1)
  • เปลี่ยนขอบเขตของ x และ y ได้ในแท็ป Limits (แท็ปที่ 2)
  • เปลี่ยนหน้าตาการแสดงผลเช่น เป็นลอการิธึมในแกน x เป็นลอการิธึมในแกน y มีกริด ไม่มีกริด ฯลฯ ได้ในแท็ป Appreance (แท็ปที่ 3)
  • เพิ่มคำสั่งอื่น ๆ ในในแท็ปที่ 4 ผมยังไม่รู้ว่ามีคำสั่งพิเศษอะไรที่ใช้ได้บ้าง ตอนนี้ยังไม่จำเป็นเลยไม่ได้สนใจ
เมื่อเราเตรียมข้อมูลใน plotdrop แล้ว เราสามารถพล็อตกราฟดูได้ทันที หากเราพอใจและต้องการจะนำกราฟไปใช้ในเอกสารอื่น ๆ สามารถเก็บพล็อตลงแฟ้มได้โดย Plot => Plot to File ... ซึ่งผู้ใช้สามารถเลือกรูปแบบของแฟ้มส่งออกได้หลากหลายเช่น EPS PS LaTeX SVG PNG และอื่น ๆ สำหรับผมถ้าจะเอามาใช้กับ LibreOffice ผมก็ใช้แฟ้ม PNG นี่แหละครับ ง่ายดี

ผมพบว่าโปรแกรม plotdrop ตัวนี้ใช้ง่ายดี ช่วยให้ผมพล็อตกราฟจาก Octave เพื่อไปใช้ต่อในงานเอกสารอื่น ๆ ได้ง่าย เราสามารถเติม Caption ต่าง ๆ ได้สะดวก พล็อตดูได้ก่อน ถ้าชอบค่อยจัดลงแฟ้ม อย่างไรก็ตาม จากการทดลองใช้พบว่าในส่วนของ Caption ต่าง ๆ ยังไม่รองรับภาษาไทยครับ หากกรอกภาษาไทยลงไป เวลาพล็อตผมจะเห็นเป็นตัวเหลื่ยม ๆ แบบนี้ [] ทุกตัวอักษรไป ถ้ารองรับภาษาไทยด้วยคงจะดี

หากใครใช้แล้วมีเทคนิคอะไรจะมาแบ่งกัน ก็อย่าลืมมาบอกกันนะครับ

วันเสาร์ที่ 14 พฤษภาคม พ.ศ. 2554

แนวคิดใหม่ (ใหม่สำหรับผม) สำหรับโปรแกรมเกี่ยวกับดนตรีไทย

ผมเคยมีแนวคิด (ผมไม่ใช่คนแรกที่คิด) ที่จะพัฒนาโปรแกรมที่สามารถช่วยงานนักดนตรีไทย ให้ประพันธ์เพลงไทยได้สะดวกขึ้นดังแสดงไว้ที่ ห้องทำงาน ในตอนนั้นความรู้ภาษาไพธอนก็มีนิดเดียว ความเข้าใจเรื่องเทคโนโลยีคอมพิวเตอร์ในงานดนตรีก็มีไม่มาก

หลังจากที่ได้รู้จักกับ Chuck ที่นำเสนอไว้ในบล๊อกก่อนหน้านี้ ก็พบว่าการเขียนโค้ดไพธอนใหม่เพื่อรับผิดชอบงานทั้งหมดตั้งแต่การเขียนเพลงไปจนถึงบรรเลงเพลงดูจะเป็นงานซ้ำซ้อน (Rebuilding the wheel) โดยเฉพาะการเขียนโค้ดไพธอนให้บรรเลงเพลงได้หลาย ๆ เสียงพร้อมกัน (Concerent Programming) ซึ่งเคยมีคนทำแล้วด้วยซ้ำโดยใช้ภาษาจาวา

เมื่อใตร่ตรองดูแล้วพบว่าปัจจุบันเรามีเทคโนโลยีที่จัดการเรื่องเหล่านี้อยู่แล้ว นั่นก็คือเทคโนโลยี MIDI หมายความว่า หากเราแปลงให้เพลงไทยที่จะประพันธ์โดยใช้โน้ตไทยให้กลายเป็นแฟ้ม MIDI ได้ เราสามารถมอบหมายให้ระบบสังเคราะห์เสียง MIDI รับผิดชอบการสร้างเสียงต่าง ๆ ไม่ว่าจะเป็นเสียงสั้น เสียงยาว เสียงพร้อมกันแบบใด ๆ ก็ตาม สิ่งที่ต้องทำก็คือการสร้างระบบสังเคราะห์เสียงดนตรีไทยในระบบ MIDI ขึ้นมานั่นเอง

ในส่วนของระบบสังเคราะห์เสียงดนตรีไทยในระบบ MIDI นั้นเข้าใจว่ามีคนทำเผยแพร่อยู่มากในรูปแบบของ Soundfont จะไม่กล่าวถึงในที่นี้ แต่จะกล่าวถึงการแปลงโน้ตเพลงไทยเป็นแฟ้ม MIDI โดยใช้ไพธอนแปลงให้อยู่ในรูปโน้ต ABC และแปลงจากโน้ต ABC เป็น MIDI ในที่สุดดังนี้

*.tmn => *.abc => *.mid

นามสกุล TMN นั้นย่อมาจาก Thai Music Notation ซึ่งจะเป็นแฟ้มข้อความธรรมดาแต่มีรูปแบบการบันทึกโน้ตตามมาตรฐานของนักดนตรีไทยดังรูป
 บรรทัดแรกคือชื่อเพลง โน้ตเพลงจะเป็นแบบ 2 บรรทัดโดยให้บรรทัดแรกเป็นโน้ตสำหรับมือซ้ายและบรรทัดที่สองเป็นโน้ตสำหรับมือขวาแต่ละบรรทัดเพลงจะมี 8 ห้อง แต่ละห้องคั่นด้วยเครื่องหมาย | และเว้นหนึ่งบรรทัดเมื่อจะขึ้นบรรทัดเพลงใหม่ บันทึกแฟ้มด้วยรหัส UTF-8

ส่วนแฟ้ม ABC ก็เป็นการบันทึกโน้ตเพลงด้วยตัวอักษรเช่นเดียวกัน แต่เป็นอักษรภาษาอังกฤษ โดยให้ C = เสียงโด D = เสียงเร เรียงกันไป ระบบนี้สามารถปรับความยาวเสียงได้ รองรับเสียง # และเสียง b และเทคนิคทางดนตรีสากลอื่น ๆ เกือบครบถ้วน รายละเอียดของระบบการบันทึกโน้ตเพลงด้วยตัวอักษร ABC นี้เรียกว่า ABC Notation ตัวอย่างของแฟ้ม ABC แสดงดังรูป
ในระบบ ABC สัญลักษณ์ | หมายถึงการกั้นห้องเช่นเดียวกัน สัญลักษณ์ z หมายถึงไม่มีเสียง หากมีตัวโน้ตสองตัวอยู่ในกรอบเดียวกันเช่น [AB] หมายความว่าให้บรรเลงโน้ต ลา และ ที พร้อม ๆ กัน

ระบบการจดบันทึกเสียงดนตรีไทยของเรามีความซับซ้อนน้อยกว่า คือไม่ระบุเทคนิคการบรรเลง (เบา - ดัง) ไม่มีครึ่งเสียง # หรือ b ความยาวเสียงของแต่ละโน้ตเท่ากันหมดคือเท่ากับหนึ่งโน้ตตัวดำ (1 จังหวะ) และแต่ละห้องจะมี 4 จังหวะตายตัวเสมอ หากมีการพลิกแพลงเป็นลูกเล่นแบบใด ๆ ลูกเล่นเหล่านี้นักดนตรีมักจะเรียนกับครูโดยตรง เรียกว่าต่อเพลง หรือไม่เช่นนั้นก็พัฒนาแนวทางของตนเองขึ้นมาแบบด้นสด (Improvise) แต่จะไม่มีระบบการจดบันทึกที่เป็นมาตรฐานเดียวกัน ดังนั้นการแปลงแฟ้ม TMN ไปเป็น ABC จึงทำได้ง่าย

พื้นฐานที่ใช้คือสร้าง Empty List ขึ้นมา แล้วให้ไพธอนอ่านแฟ้ม TMN ไปทีละบรรทัด ในแต่ละบรรทัดเมื่อพบตัวอักษรก็เทียบอักษรไทยเข้ากับอักษรอังกฤษ เช่นเทียบ "ด" เป็น "C" เป็นต้น ถ้าหากตัวอักษรมีตัวเปลี่ยนขั้นเสียง เช่น ดํ จะให้หมายถึงเสียง โดสูง หรือ "c" ในระบบ ABC และ ดฺ จะให้หมายถึงเสียง โดต่ำ หรือ "C," ในระบบ ABC ดำเนินการเช่นเดียวกันกับโน้ตเพลงบรรทัดที่สอง

L1 = [];
 for i in range(len(line1)):
  if line1[i]=='|':
   L1.append('|');

  if line1[i]==':':
   L1.append(':');

  if line1[i]=='-':
   L1.append('z');

  if line1[i]==u'ด':
   if line1[i+1]==u'\u0e4d':
    L1.append('c');
   elif line1[i+1]==u'\u0e3a':
    L1.append('C,');
   else:
    L1.append('C'); 

เมื่อได้ List L1 สำหรับบรรทัดที่หนึ่งและ L2 สำหรับบรรทัดที่สองมาแล้วก็นำ List ทั้งสองมาเทียบกัน หากในจังหวะเสียงเดียวกันมีโน้ตทั้งสองบรรทัด ก็ให้บรรเลงโน้ตทั้งสองตัวพร้อมกัน ถ้ามีเพียงโน้ตตัวเดียวก็บรรเลงตัวเดียว ถ้าไม่มีโน้ตทั้งคู่ ก็ให้เงียบเสีย

L = [];
 for i in range(len(L1)):
  if L1[i]=='|':
   L.append('|');
  elif L1[i]==':':
   L.append(':');
  elif L1[i]=='z' and L2[i]=='z':
   L.append('z');
  elif L1[i]=='z' or L2[i]=='z':
   if L1[i]=='z':
    L.append(L2[i]);
   else:
    L.append(L1[i]);

  else:
   L.append('['+L1[i]+L2[i]+']');



นี่คือการแปลงโน้ตอักษรไทยไปเป็นโน้ตอักษรภาษาอังกฤษในระบบ ABC ผมตั้งชื่อโปรแกรมนี้ว่า tmn2abc (Thai Music Notation to ABC) เรียกใช้ดังนึ้

# python tmn2abc test.tmn test.abc

หลังจากนี้เราสามารถใช้เครื่องมือที่มีอยู่แล้วคือโปรแกรม abc2midi แปลงจากแฟ้ม ABC นี้ให้เป็น MIDI ได้ทันที

# abc2midi test.abc -o test.mid

สิ่งที่ต้องการจะทำเพิ่มเติมคือ
  1. รองรับระบบโน้ตบรรทัดเดียว สำหรับโน้ตซอ โน้ตขลุ่ย ฯลฯ
  2. เพิ่มการระบุชั้นเพลง คือ หนึ่งชั้น สองชั้น สามชั้น
  3. เพิ่มส่วนกำหนดเครื่องดนตรี แต่เรื่องนี้ต้องศึกษามาตรฐาน MIDI เกี่ยวกับการกำหนดช่องสัญญาณและโปรแกรมเพิ่มเติม
ก่อนหน้านี้เคยเขียนโปรแกรมนี้ไว้แล้ว แต่ตอนสำรองข้อมูลก่อนลงระบบใหม่เมื่อตอนทดสอบ Ubuntu ไม่สมบูรณ์ โปรแกรมทั้งชุดจึงหายไปหมด ตอนนี้เขียนใหม่แล้วเลยอัพโหลดไว้เสียเลย ท่านใดสนใจสามารถดูต้นฉบับโปรแกรมได้ sourceforge.net ครับ หากมีปัญหาหรือข้อแนะนำใด ๆ หากจะทิ้ง Comment ไว้ที่ Blog นี้ได้ก็จะขอขอบคุณล่วงหน้าครับ

วันเสาร์ที่ 23 เมษายน พ.ศ. 2554

ฆ้อง MIDI ด้วย Chuck

แนวคิดหนึ่งที่อยากทำมานานก็คือพัฒนาโปรแกรมที่ช่วยให้นักดนตรีไทยประพันธ์เพลงได้สะดวกขึ้น ทางหนึ่งที่ทำได้ก็คือเขียนโปรแกรมรับข้อมูล MIDI จากคีย์บอร์ด แล้วสังเคราะห์เสียงตามคำสั่ง

Chuck มีความสามารถในการสื่อสารกับเครื่องดนตรีอิเล็กทรอนิกส์ทาง MIDI โดยใน Chuck จะมีคลาส MIDI ในตัว การเรียกใช้ความสามารถทาง MIDI ใน Chuck ทำได้ดังตัวอย่าง (จาก Official Chuck Tutorial)

MidiIn min;
MidiMsg msg;

// open midi receiver, exit on fail
if ( !min.open(0) ) me.exit(); 

while( true )
{
    // wait on midi event
    min => now;

    // receive midimsg(s)
    while( min.recv( msg ) )
    {
        // print content
     <<< msg.data1, msg.data2, msg.data3 >>>;
    }
}

จากการทดลองพบว่า
  • msg.data1 คือข้อมูล Channel และคำสั่ง MIDI อื่น ๆ เช่น Note-On, Note-Off
  • msg.data2 คือขั้นเสียง
  • msg.data3 คือความดัง (Velociy)
เราสามารถใช้ msg.data# มาควบคุมการสังเคราะห์เสียงของ Chuck ที่เราทำไว้เมื่อคราวก่อนได้ดังนี้

MidiIn min;
MidiMsg msg;
min.open(0) => int AmIOpen;

if(!AmIOpen)
{
 <<< "Can not Open MIDI Input Port" >>>;
 me.exit();
}else
{
 while(true)
 {
  min => now;
  while(min.recv(msg))
  {
   <<< msg.data1,msg.data2,msg.data3,"MIDI Message">>>;
   if(msg.data3!=0)
   {
    if(msg.data2==59)
    {
     Machine.add("gong01.ck");
    }
    if(msg.data2==60)
    {
     Machine.add("gong02.ck");
    }
   }
  }
 }
}

เราใช้ msg.data# เป็นเงื่อนไขให้ Chuck เพิ่ง shred ของฆ้องแต่ละลูกเข้าไปใน Chuck ได้ด้วยคำสั่ง

Machine.add("gong01.ck");

เมื่อ gong01.ck gong02.ck ... เป็นสคริปต์สังเคราะห์เสียงฆ้องแต่ละลูก แต่นี่หมายความว่าที่เครื่องคอมพิวเตอร์จะต้องรัน Chuck อยู่แล้วด้วยคำสั่ง

# chuck --loop

ด้วยวิธีนี้ Chuck จะเป็นคนจัดการเรื่องการสังเคราะห์เสียงหลาย ๆ เสียงพร้อม ๆ กันได้เอง เราไม่ต้องเขียนโปรแกรมขึ้นมาใหม่เลย นั่นหมายความว่าหากเราสามารถสร้างสคริปต์สังเคราะห์เสียงเครื่องดนตรีไทยชนิดต่าง ๆ ได้ ชุดโปรแกรมชุดนี้ก็สามารถรับสัญญาณ MIDI มาตรฐานจากโปรแกรมทั่ว ๆ ไปและสร้างเสียงดนตรีไทยทั้งวงได้ทันที

วันพฤหัสบดีที่ 21 เมษายน พ.ศ. 2554

การสังเคราะห์เสียงด้วย Chuck

ก่อนหน้านี้ได้ลองใช้ Puredata (pd) สังเคราะห์สัญญาณ Decay Sinusoid ซึ่งมีลักษณะเสียงคล้ายเสียงฆ้อง ตั้งใจว่าจะใช้สังเคราะห์เสียงดนตรีไทย แต่ว่าการใช้งาน Python ร่วมกับ Puredata แม้ว่าจะทำได้ แต่ดูเหมือนว่าจะทำได้ยาก เนื่องจากมีเอกสารกำกับน้อย น่าจะเหมาะกับคนที่ใช้ Python จนคล่องแคล่วแล้วมากกว่า

ผมก็เลยกลับมาพิจารณา Chuck อย่างจริงจังอีกครั้งหนึ่งหลังจากที่ได้เอาคู่มือมาพลิกผ่าน ๆ เมื่อหลายเดือนก่อนพบว่าน่าจะสามารถใช้งานร่วมกับ Python ได้ง่ายกว่า

Chuck คืออะไร
Chuck คือ Real-Time Audio Programming Language สำหรับงานสังเคราะห์และเรียบเรียงเสียงสังเคราะห์ โปรแกรมที่เขียนด้วย Chuck จะรันใน Chuck Virtual Machine ที่มีความสามารถในการรันหลาย ๆ โปรแกรม (Shred) ได้พร้อม ๆ กัน และแต่ละ Shred ก็สามารถสื่อสารเพื่อประสานงานกันได้ ท่านที่สนใจสามารถอ่านรายละเอียดเพิ่มเติมได้ที่ http://chuck.cs.princeton.edu/ และ http://kijjaz.exteen.com/20070702/entry (ภาษาไทย)

ตัวอย่างสคริปต์ที่รันใน Chuck

SinOsc s => dac;
440 => s.freq;
0.5 => s.gain;
2::second => now;

สคริปต์ข้างต้นนี้จะกระทำ 2 สิ่งคือ ต่อไซนูซอยด์ออสซิลเลเตอร์ (s) เข้ากับซาวด์การ์ด (dac) ตั้งค่าความถี่และขนาดสัญญาณเป็น 440 เฮิรทซ์และ 0.5 ตามลำดับ จากนั้นให้เวลาในสคริปต์เดินหน้าไป 2 วินาทีส่งผลให้เกิดเสียงขึ้น 2 วินาที

เรื่องเวลานี่ทีแรกผมไม่เข้าใจ แต่ถ้าเราเปรียบเทียบกับการเดินหน้าเส้นเทปไป 2 วินาทีก็จะเข้าใจง่ายขึ้น สัญญาณเสียงนั้นอยู่ในเทปอยู่แล้ว แต่ถ้าเส้นเทปไม่เดินผ่านหัวเทปก็จะไม่เกิดเสียงขึ้น

หลังจากเขียนสคริปต์เสร็จแล้วก็บันทึกไว้ในแฟ้มชื่อ foo.ck ก็ได้ (ตามขั้นตอนใน Tutorial เป๊ะ ๆ) ถ้าจะให้ Chuck ทำงานก็เรียกคำสั่ง

# chuck foo.ck

ก็จะได้ยินเสียงไซนูซอยด์ดังออกมาจากลำโพง

นอกจากนี้โปรแกรม Chuck ยังมี Built-in ฟังก์ชันและออบเจกต์ที่ใช้ประมวลผลสัญญาณเสียงอีกมากมาย ที่ใช้บ่อย ๆ ก็คงจะเป็นพวกตัวกรองต่าง ๆ ในงานที่ผมใช้สังเคราะห์เสียง Decay Sinusoid ที่มีเสียงคล้ายเสียงฆ้องนั้น เมื่อพิจารณาแล้วสามารถใช้ตัวกรองแบบ Bi-Quad ได้ การใช้ตัวกรอง Bi-Quad ใน Chuck สามารถทำได้ดังนี้

Impulse i => BiQuad f => dac;
0.000 => f.b0;
0.04128430 => f.b1;
0.000 => f.b2;
1.0000 => f.a0; 
-1.99824581 => f.a1;
0.99995098 => f.a2;

0.3 => i.next;
5::second => now;

ในสคริปต์นี้
  • บรรทัดแรกจะเรียกใช้ออบเจกต์อิมพัลส์และเชื่อมต่ออิมพัลส์เข้ากับตัวกรอง Bi-Quad และต่อเอาต์พุตจาก Bi-Quad เข้ากับซาวด์การ์ดของเรา 
  • บรรทัดที่ 2 - 7 ระบุค่า Coefficient ของตัวกรองทั้ง 6 ตัว 
  • บรรทัดที่ 8 ระบุขนาดของอิมพัลส์ที่จะส่งเข้าตัวกรอง
  • บรรทัดที่ 9 ระบุช่วงเวลาที่จะให้ Chuck ส่งเสียงออกมา
นี่คือเสียงสังเคราะห์ของฆ้องวงเล็กลูกแรก ผมบันทึกแฟ้มเป็น gong01.ck ถ้าต้องการให้ Chuck สร้างเสียงฆ้องลูกนี้ก็เรียกใช้คำสั่ง

# chuck gong01.ck

ก็จะได้ยินเสียงคล้าย ๆ เสียงฆ้องดังออกมา

จากที่ทดลองพบว่าการใช้ Chuck นั้นง่ายพอ ๆ กับ puredata แต่ว่าถ้าคิดถึงการเชื่อมต่อกับโปรแกรมอื่น ๆ เพื่อให้ทำงานร่วมกันหลังฉากโดยไม่ต้องให้ผู้ใช้งงกับหน้าต่างมาก ๆ โปรแกรม Chuck ดูเหมือนจะเป็นทางเลือกที่ดีกว่า ต่อไปนี้ก็จะลองใช้ Chuck เพื่อสังเคราะห์เสียงอื่น ๆ ต่อไปดู

วันอังคารที่ 5 เมษายน พ.ศ. 2554

ติดตั้งเครื่องพิมพ์ Canon LBP3050 บน Debian (wheezy)

[ T_T สุดท้ายก็ไม่สามารถพิมพ์ภาพได้ครับ นอกจากนี้เมื่อตอนทดสอบยังไม่ได้ทดสอบกับภาษาไทย พอทดสอบกับภาษาไทยก็ยิ่งแย่ ตัวหนังสือตีกันไปหมด ตอนนี้ยกธงขาวแล้ว แฟนจะเหน็บก็ต้องยอมแล้วล่ะ]

ที่บ้านมีเครื่องพิมพ์อยู่เครื่องหนึ่งคือ Canon LBP3050 เป็นของแฟน พอจัดห้องทำงานที่บ้านใหม่แฟนก็ยกเครื่องพิมพ์เครื่องนี้ให้ แต่ว่าเวลาเอามาใช้กับเครื่องคอมพ์ของผมดันพิมพ์งานไม่ออก!! ทั้ง ๆ ที่ Linux ตรวจเจอเครื่องพิมพ์

งานเข้าล่ะครับ เพราะแฟนผมชอบเหน็บให้เจ็บใจเล่น ๆ เวลาที่ Linux ทำอะไรบางอย่างไม่ได้ในขณะที่ Windows ของเธอทำได้ (ผมก็เหน็บเธอเหมือนกันเวลาที่ Windows ทำอะไรบางอย่างไม่ได้ในขณะที่ Linux ทำได้ ก็เจ๊ากันไปนะ ฮิ ฮิ)

ผมพบว่าระบบตรวจจับเครื่องพิมพ์อัตโนมัติของ Linux ใช้ไม่ได้กับเครื่องพิมพ์บางจำพวก และเจ้า Canon LBP3050 เครื่องนี้ก็เป็นหนึ่งในนั้นครับ ในกรณีของ Canon LBP3050 นั้นผมคิดว่าเป็นเพราะ LBP3050 เป็นเครื่องพิมพ์ที่ใช้เทคโนโลยี CAPT - Canon Advanced Printing Technology ซึ่งทาง Canon โฆษณาว่าสามารถพิมพ์งานได้เร็ว และพิมพ์งานที่ขนาด (หน่วยความจำ) ใหญ่ได้โดยไม่ต้องเพิ่มหน่วยความจำ ... เอาเถอะ ... จะเป็นอะไรก็ช่าง เอาเป็นว่าถ้าจะใช้เครื่องพิมพ์ตระกูล CAPT จะต้องมี CAPT Driver ไม่ว่าจะเป็นบน Linux Mac หรือ Windows ก็ตาม ทีนี้ในระบบ Linux เราใช้ CUPS - Common UNIX Printing Solution ก็แปลว่าเจ้า CAPT Driver ที่ว่าจะต้องคุยกับ CUPS ให้รู้เรื่อง และการทำให้มันคุยกันรู้เรื่อง ก็ต้องใช้การติดตั้งที่ถูกต้องจึงจะสำเร็จ

การติดตั้งมีด้วยกัน 4 ขั้นตอนคือ
  1. จัดหาแพคเกจที่จำเป็น
  2. ติดตั้งแพคเกจดังกล่าว
  3. ติดตั้งเครื่องพิมพ์ลงบน CUPS
  4. ผูกเครื่องพิมพ์เข้ากับ Service ccpd
  5. ใช้ insserv สั่งให้เริ่มบริการ ccpd โดยอัตโนมัติทุกครั้งที่มีการเริ่มระบบใหม่
  6. เรียกใช้ ccpd เพื่อใช้งานเครื่องพิมพ์
รายละเอียดการติดตั้งมีอยู่ในแฟ้มเอกสารที่มาพร้อมกับแพคเกจที่จำเป็น แต่จะขอบันทึกไว้เผื่อว่าจะได้ใช้ในภายหลัง โดยจะเขียนอธิบายตามรายการข้างต้นไปทีละข้อ ๆ

จัดหาแพคเกจที่จำเป็น
ดาว์นโหลดได้ที่เว็บไซต์ Canon ออสเตรเลียเลยครับ (ค้น ๆ ดูมีที่สิงค์โปร์ด้วย ส่วนเมืองไทยไม่มีนะครับ) แฟ้มที่โหลดได้คือ CAPT_Printer_Driver_for_Linux_v220_uk_EN.tar.gz ขนาดประมาณ 33 MB
เมื่อได้มาแล้วก็แตกแฟ้มไว้ที่ไหนสักแห่ง ซึ่งต่อไปจะขอเรียกไดเรคทอรีนี้ว่า #Canon เพื่อความสะดวก

ติดตั้งแพคเกจดังกล่าว
เมื่อแตกแฟ้มแล้วจะพบแพคเกจ
  • cndrvcups_common_2.20-1_i386.deb  และ
  • cndrvcups_capt_2.20-1_i386.deb
อยู่ในไดเรคทอรี #Canon/CAPT_Printer_Driver_for_Linux_v220_uk_EN/32-bit_Driver/Debian ติดตั้งแพคเกจตามลำดับที่แสดงไว้ข้างต้น โดยใช้ GDebi หรือ dpkg ก็ได้ ในกรณีของผมเองปัจจุบันใช้ Debian wheezy อยู่ จะขาดแพคเกจที่จำเป็นไปหนึ่งแพคเกจคือ gs-eps ผมก็แก้ไขโดยการเพิ่ม Repository ของ squeeze ลงไปใน sourcelist ก็พบแพคเกจ gs-eps ซึ่งเป็น Transition Package อยู่ ติดตั้งแพคเกจนี้เข้าไปก็ใช้ได้แล้ว

ติดตั้งเครื่องพิมพ์ลงบน CUPS
เมื่อติดตั้งแพคเกจทั้งสองเสร็จสิ้นให้เริ่ม CUPS ใหม่ซึ่งทำได้โดยใช้คำสั่ง

# /etc/init.d/cups restart

จากนั้นก็เพิ่มเครื่องพิมพ์ลงในคิวของ CUPS ด้วยคำสั่ง

#  lpadmin -p LBP3050 -m CNCUPSLBP3050CAPTK.ppd -v ccp://localhost:59687 -E

เมื่อข้อมูลหลังตัวเลือกต่าง ๆ มีความหมายดังนี้
  • -p [Printer Name] ระบุชื่อที่ระบบเราจะใช้เรียก จะตั้งว่าอะไรก็ได้
  • -m [ppd file] ระบุแฟ้ม ppd ที่เป็นตัว Driver ของเครื่องพิมพ์ของเรา ในรายละเอียดการติดตั้งที่ให้มาพร้อมกับแพคเกจจะบอกว่าเครื่องพิมพ์ของเราจะต้องใช้แฟ้ม ppd ตัวไหน ส่วนรายการเครื่องพิมพ์สำหรับ wheezy จะอยู่ที่ /usr/share/cups/model
  • -v [Device URI] ระบุ URI ที่เราจะให้เป็นทางผ่านข้อมูลสำหรับเครื่องพิมพ์ของเรา URI ที่เขียนไว้ตรงนี้ลอกมาจากรายละเอียดการติดตั้ง จากที่ค้น ๆ ดู ในกรณีทั่ว ๆ ไป ใคร ๆ ก็ใช้ URI นี้ มีบางกรณีของ UBUNTU ที่จะใช้ URI เป็น FIFO0 อันนี้ยังไม่ได้ลองว่าใช้ได้หรือไม่
  • -E เป็นการสั่ง Enable เครื่องพิมพ์
กระบวนการทั้งหมดนี้น่าจะสามารถทำด้วย GUI ได้ด้วย ระบบ->ดูแลระบบ->เครื่องพิมพ์ เพื่อเรียกเครื่องมือตั้งค่าเครื่องพิมพ์ของ GNOME โดยที่ตัวเลือก -E ข้างหลังสุดของคำสั่งนั้นคือการ Enable เครื่องพิมพ์นั่นเอง [ยังไม่ได้ลอง]

หลังจากนี้ถ้าเราเปิดเครื่องมือการตั้งค่าเครื่องพิมพ์ของ GNOME ดู ควรจะเห็นเครื่องพิมพ์ของเราปรากฏอยู่ในรายการด้วย ดังเช่นที่แสดงในรูปนี้ (เราต้องรู้ชื่อของเครื่องพิมพ์ของเรา เพราะเราเป็นคนตั้งชื่อเองด้วยตัวแปรหลังตัวเลือก -p ของคำสั่ง lpadmin)
ตอนนี้ยังใช้เครื่องพิมพ์ไม่ได้นะครับ 
[ตามความเข้าใจของผม] เพราะเราใช้ CAPT ซึ่งมีการ "ยำ" ข้อมูลก่อนส่งออกเครื่องพิมพ์ ดังนั้นถ้าเราส่งไปตรง ๆ เหมือนส่งเข้าเครื่องพิมพ์ประเภทอื่น ๆ เครื่องพิมพ์ก็จะไม่เข้าใจ จึงจำเป็นต้องมีข้อต่อไป

ผูกเครื่องพิมพ์เข้ากับ Service ccpd 
ซึ่งทำได้โดย

# ccpdadmin -p LBP3050 -o /dev/usb/lp0

นี่เป็นการบอก ccpd ว่าเครื่องพิมพ์ CUPS ชื่อ LBP3050 นั้นต่ออยู่กับช่องทางไหน ทีนี้ในคู่มือการติดตั้งเตือนไว้เหมือนกันว่า ถ้ามีเครื่องพิมพ์ USB หลายเครื่อง ลำดับก่อนหลังของ lp จะเรียงตามลำดับการต่อเครื่องพิมพ์เข้ากับคอมพิวเตอร์ ในอินเตอร์เนตมีคำแนะนำการตั้งค่าบริการ ccpd ให้ตั้งค่าเองเมื่อเสียบสาย USB ของเครื่องพิมพ์เข้ากับคอมพิวเตอร์ (แต่เป็นของ UBUNTU นะ) พอดีผมมีเครื่องพิมพ์เครื่องเดียวและต่ออยู่ตลอดเลยไม่สนใจสถานการณ์ที่ว่านี้

หลังจากนี้ ถ้าเริ่มบริการ ccpd ก็จะสามารถพิมพ์ได้เลย การเริ่มบริการ ccpd ทำได้โดย

# /etc/init.d/ccpd start

หรือไม่ก็ใช้ ระบบ->ดูแลระบบ->บริการระบบ เริ่มบริการ ccpd ก็ได้ ถ้าอยากให้บริการ ccpd เริ่มเองทุกครั้งก็ไปขั้นตอนถัดไป

ใช้ insserv สั่งให้เริ่มบริการ ccpd โดยอัตโนมัติทุกครั้งที่มีการเริ่มระบบใหม่
ในการนี้เราจะต้องแก้ไขแฟ้ม /etc/init.d/ccpd โดยการเพิ่มคอมเมนต์ต่อไปนี้เข้าไปในบรรทัดที่สามของ ccpd คอมเมนต์ที่จะเติมนี้ขอแนะนำว่าให้ไปคัดลอกจากต้นฉบับที่คู่มือการติดตั้ง ซึ่งอยู่ใน #Canon/CAPT_Printer_Driver_for_Linux_v220_uk_EN/Doc โดยตรงจะดีที่สุด

### BEGIN INIT INFO
# Provides:         ccpd
# Required-Start:   $local_fs $remote_fs $syslog $network $named
# Should-Start:     $ALL
# Required-Stop:    $syslog $remote_fs
# Default-Start:    3 5
# Default-Stop:     0 1 2 6
# Description:      Start Canon Printer Daemon for CUPS
### END INIT INFO
ผมก็สงสัยว่ามันเป็นคอมเมนต์ แล้วไปเติมมันทำไม เลยเข้าไปอ่านรายละเอียดของ insserv จึงได้ทราบว่าเจ้า insserv นี่จะเข้าไปอ่านคอมเมนต์ในสคริปต์แล้วเติมบริการนี้เข้าไปในรายการที่จะต้องเริ่มอัตโนมัติตอนเริ่มระบบ แต่ว่าแค่เติมคอมเมนต์นี่เข้าไปยังไม่พอ จะต้องเรียกคำสั่ง

# insserv ccpd

ด้วย ในระบบของผมมี Warning ขึ้นมานิดหน่อย แต่อ่าน ๆ ดูแล้วคิดว่าไม่สำคัญมาก ก็ปล่อยผ่านไป ทีนี้ถ้าท่านค้นข้อมูลจากอินเตอร์เนตท่านอาจจะพบวิธีการแก้ไขสคริปต์ที่ว่านี้หลาย ๆ แบบ ผมก็ลองแล้วปรากฏว่าไม่เวิร์ก อาจจะด้วยหลาย ๆ เหตุผล เช่นผมคัดลอกสคริปต์มาไม่สมบูรณ์หรืออะไรก็สุดแท้แต่ สุดท้ายผมมาใช้สคริปต์ต้นฉบับตามคู่มือของ Canon เองนั่นแหละ ถึงได้พิมพ์ได้ในที่สุด

ถึงขั้นตอนนี้ถ้าทั้ง cups และ ccpd กำลังทำงานอยู่ เราก็สามารถพิมพ์งานได้เลย

ระบบ CAPT ให้เครื่องมือกับเรามาอีก 2 อย่างก็คือ
  • captstatusui และ
  • cngplp (ผมเดาว่าคงจะเป็น Canon General Purpose lp)
# captstatusui -P LBP3050

สั่งงานพิมพ์จากบรรทัดคำสั่ง (แทน lpr) ได้โดย

# cngplp -p [Filename to be printed]


หมายเหตุ
ในคู่มือระบุว่าระบบ CAPT จะทำงานได้สมบูรณ์จำเป็นจะต้องติดตั้ง libstdc++5 เสียก่อน แม้ว่าระบบของผมมี libstdc++6 อยู่แล้ว แต่เพราะผมไม่ทราบแน่ชัดว่าใช้ได้หรือไม่ จึงติดตั้ง libstdc++5 ลงไปอยู่ดี

ที่พิมพ์ได้ตอนนี้คือ PS PDF และ ODT ที่ไม่มีรูปภาพ :o ตอนนี้กำลังหาทางอยู่ว่าถ้าจะพิมพ์รูปด้วยจะต้องทำอย่างไร :(

ที่ต้องลำบากลำบนขนาดนี้ เป็นเพราะว่าต้องพยายามใช้เครื่องที่มีอยู่ในเกิดประโยชน์ หากเป็นการพิจารณาซื้อเครื่องใหม่ ท่านอาจมีตัวเลือกที่สะดวกกว่าโดยเฉพาะเครื่องพิมพ์ที่มีรายการ Driver อยู่ใน CUPS อยู่แล้วเป็นต้น

วันเสาร์ที่ 26 กุมภาพันธ์ พ.ศ. 2554

การสร้างสัญญาณ Decaying Sinusoid ด้วยตัวกรองดิจิตอลโดยใช้ pd

แบบจำลองของเสียงจากเครื่องดนตรีแบบหนึ่งคือ Decaying Sinusoid ซึ่งก็คือสัญญาณตระกูลไซนูซอยด์ที่มีขนาดลดลงด้วยอันดับเอกโพเนนเชียลเมื่อเวลาผ่านไป สัญญาณลักษณะนี้สามารถเขียนเป็นสมการได้
amath y(t) = A*e^{-b*t}*sin( omega_{c} * t ) endamath
เมื่อ amath A endamath คือขนาดของสัญญาณ amath a endamath คืออัตราการลดทอน amath omega_{c} endamath คือความถี่มีหน่วยเป็น rad/sec และ amath t endamath คือเวลา
สัญญาณ Decaying Sinusoid (เส้นสีแดงและเส้นสีเขียวเป็นเพียงเส้นนำสายตาเท่านั้น)
กลยุทธ์การสังเคราะห์เสียงลักษณะนี้ก็คือการใช้ตัวกรองที่มีผลตอบสนองอิมพัลส์ (Impulse Response) ที่มีหน้าตาแบบนี้เลย คือถ้าใส่อิมพัลส์เข้าไป ให้ตัวกรองส่งสัญญาณหน้าตาแบบนี้ออกมา ซึ่งเราสามารถสร้างตัวกรองดังกล่าวได้โดยใช้กรรมวิธีการออกแบบตัวกรองดิจิตัลแบบอิมพัลส์ไม่เปลี่ยนแปลง (Impulse Invariant Method)

ด้วยวิธี Impulse Invariant Method เราสามารถเขียนผลตอบสนองของตัวกรองดิจิตัลในรูปแบบสัญญาณดิสครีตได้ดังนี้
amath y(n) = A*e^{-k*n}*sin(omega_{d}*n) endamath
เมื่อ amath k endamath เป็นอัตราลดทอนที่แปลงมาจาก amath b endamath amath omega_{d} endamath เป็นความถี่ในโดเมนดิสครีตมีหน่วยเป็น rad/sample และ amath n endamath คือ index ของสัญญาณดิสครีต

ตัวกรองที่มีผลตอบสนองอิมพัลส์เป็นสัญญาณนี้คือตัวกรองที่มี Transfer Function - H(z) เป็น z-Transform ของสัญญาณนี้นั่นเอง ซึ่งจากตารางการแปลง z ทำให้เราทราบว่า
amath A*e^{-k*n}*sin(omega_{d}*n) -> (e^{-k}*sin(omega_{d})*z^{-1})/(1-2*e^{-k}*cos(omega_{d})*z^{-1}+e^{-2*k}*z^{-2}) endamath
เราสามารถสร้างตัวกรองนี้ใน puredata ได้โดยตรงโดยใช้ออบเจ็ค fexpr~ สร้างสมการผลต่าง (Difference Equation) ไดัเป็น
amath y(n) = e^{-k}*sin(omega_{d})*x(n-1)+2*e^{-k}*cos(omega_{d})*y(n-1)-e^{-2*k}y(n-2) endamath
และต่อเป็นแพตช์ใน puredata ได้ดังรูป

ซึ่งเราสร้างขึ้นมาเป็น sub-patch ให้รับอินพุตได้ 4 ค่าคือ
  1. อิมพัลส์ (x1 ใน fexpr~) เป็นตัวกระตุ้นตัวกรอง ซึ่ง puredata patch สำหรับอิมพัลส์สามารถดูได้จากผลงานเมื่อคราวก่อน
  2. อัตราการลดทอน (f2 ใน fxpr~ - หมายถึงอินพุตที่ 2 และเป็นตัวแปรประเภท Float) amath e^{-k} endamath
  3. ความถี่ในโดเมนดิสครีต หน่วยเป็น rad/sample (ระบุผ่าน f3 - amath cos(omega_{d}) endamath และ f4 - amath sin(omega_{d}) endamath)
  4. อัตราชักตัวอย่าง (Sampling Rate -fs) ไว้ใช้คำนวณค่า amath omega_{d} endamath ก่อนส่งเข้าไปใน fexpr~
ในโอกาสต่อไปจะนำเสนอการนำ Decaying Sinusoid ไปสร้างเสียงสังเคราะห์ที่ซับซ้อนมากขึ้น

วันอังคารที่ 8 กุมภาพันธ์ พ.ศ. 2554

การสร้าง Impulse (Discrete) ใน pd เพื่อใช้ทดสอบตัวกรอง

ผมหัดใช้ pd มาได้สักพักแล้ว ตอนนี้กำลังทำโครงงานที่สอง (เลยนึกได้ว่าไม่เคยบันทึกโครงงานแรกลงในบล๊อกเลย) ในโครงงานนี้เราจะสร้างตัวกรอง (Filter) เพื่อสังเคราะห์สัญญาณเสียงขึ้นมา ซึ่งตัวกรองจะทำงานได้ต้องมีอินพุต และอินพุตที่เราจะใช้นั้นเป็นอิมพัลส์ (Impulse) ในดิสครีตโดเมน

ใน pd-extended นั้นจะมีออบเจ็ค dirac~ ให้ใช้อยู่ แต่เนื่องจากผมใช้เดเบียนซึ่งใน Repository มีแต่ pd-vanilla ซึ่งไม่มีออบเจ็ค dirac~ ให้ใช้ ก็ต้องสร้างเอง

การสร้างได้แนวทางมาจาก http://www.mail-archive.com/pd-list@iem.at/msg12771.html และหลังจากลองผิดลองถูกมาได้สักพัก ก็สามารถสร้าง Discrete Impulse ขึ้นมาจนได้ดังรูป
หลักการทำงานก็คือ
  1. รับข้อความ \$1 0 มาจาก inlet เมื่อ \$1 เป็นตัวแปรที่ส่งเข้ามาเพื่อใช้เป็นขนาดของอิมพัลส์
  2. ส่งข้อความไปให้ vline~ สร้างสัญญาณเป็น \$1 ทันทีที่ได้รับข้อความ
  3. หลังจากนั้น ข้อความ \$1 0 จะส่งทริกเกอร์ไปยังการคำนวณคาบเวลา T=1/fs (ในที่นี้ใช้ 1.5/fs แทน เผื่อไว้กันเหนียว)
  4. สร้างข้อความ 0 0 \$1 เมื่อ \$1 ระยะเวลาเป็นหน่วยมิลลิวินาทีที่รอก่อนที่ vline~ จะสั่งให้ตั้งค่าเป็นศูนย์ เราพยายามจะตั้งให้ได้ 1 Sample=1/fs พอดี แต่เพื่อผลที่แน่นอนใน Patch นี้เราเลยเลือกใช้เป็น 1.5/fs แทน
  5. ส่งข้อความไปให้ vline~ เพื่อตั้งค่าให้เป็น 0
  6. ส่งออกเป็น Stream ไปที่ outlet~
ทดลองแล้วใช้ได้ผลดี ถ้าต้องการตรวจสอบผล ให้เพิ่มออบเจ็ค print~ ไปที่เอาต์พุตของ vline~ และให้รับ bang มาจากข้อความไหนก็ได้ใน Patch ด้วย จะเห็นผลลัพธ์ดังรูปต่อไปนี้
ต่อไปนี้ก็สามารถทดสอบตัวกรองต่าง ๆ ได้ ซึ่งถ้ามีอะไรน่าสนใจก็จะนำมาเสนอต่อไป

วันอังคารที่ 18 มกราคม พ.ศ. 2554

การเขียนวิทยานิพนธ์ ม.ข. ด้วย LaTeX - สร้างสารบัญ

ข้อกำหนดหนึ่งของวิทยานิพนธ์บัณฑิตศึกษา มหาวิทยาลัยขอนแก่นคือสารบัญ
ข้อกำหนดคือ
  1. สารบัญ และส่วนประกอบอื่น ๆ เช่น กิตติกรรมประกาศ มีศักดิ์ศรีเทียบเท่าบท ดังนั้นหัวข้อของสารบัญจึงใช้หัวข้อของบท แต่ไม่ต้องมีเลขบท คือเป็นตัวอักษรแบบตัวหนาขนาด 18 พอยต์ (Bold 18 points) อยู่กึ่งกลางหน้ากระดาษ
  2. หากสารบัญมีมากกว่าหนึ่งหน้า หน้าต่อ ๆ ไปของสารบัญจะต้องขึ้นหัวข้อว่า "สารบัญ (ต่อ)" ด้วยอักษรตัวหนาขนาด 18 พอยต์เช่นเดียวกัน (ข้อนี้ไม่มีคำสั่งเป็นลายลักษณ์อักษร แต่ตัวอย่างที่มีให้เป็นแบบนี้ และวิทยานิพนธ์ทุก ๆ เล่มที่ผมเข้าถึงได้ ก็เป็นแบบนี้)
  3. เนื้อสารบัญควรบอก บทที่ ชื่อบท และเลขหน้าของบทนั้น ๆ เริ่มตั้งแต่บทคัดย่อ จนไปจบที่ประวัติผู้เขียนเป็นรายการสุดท้าย ใช้อักษรแบบธรรมดาขนาด 14 พอยต์ (Normal 14 points)
  4. หัวข้อย่อยของแต่ละบทนั้น แสดงลึกไม่เกิน 1 ระดับ คือสารบัญจะแสดงรายละเอียดถึง 1.1 หมายถึง บทที่ 1 หัวข้อที่ 1 เท่านั้น จะไม่แสดงรายละเอียดถึง 1.1.1
  5. หัวข้อย่อยควรจัดให้ตรงกับบท (ไม่บังคับ แต่ตัวอย่างเป็นแบบนี้)
  6. เลขลำดับของหัวข้อย่อย จะมีหรือไม่มีก็ได้
  7. ระยะห่างระหว่างชื่อบท หรือหัวข้อสารบัญ ถึงบรรทัดแรกของสารบัญ ให้เว้น 2 บรรทัด (ของอักษรขนาดกี่พอยต์?)
ค้นไปค้นมา พบว่าไม่ต้องเขียนอะไรมาก เอา แพคเกจที่มีคนทำไว้แล้วมาใช้ก็ได้ แพคเกจที่หยิบมาใช้คือ titletoc ซึ่งติดตั้งมาพร้อมกับ titlesec ซึ่งอยู่ในแพคเกจเดเบียน texlive-latex-extra

สิ่งที่ต้องใช้ใน preemble ก็คือ เรียกคลาสที่จำเป็นขึ้นมา
\RequirePackage{titletoc}
\RequirePackage{ifthen}
\RequirePackage{lineno}

ตั้งค่าความลึกของหน้าสารบัญใหม่เป็น 1 (คือลงลึกแค่ชั้นเดียว) และตั้งค่าคำสั่ง \tableofcontents ใหม่
\setcounter{tocdepth}{1}
\renewcommand\tableofcontents{%
% Header of the first page of the TOC
\chapter*{\contentsname\@mkboth{\MakeUppercase\contentsname}
{\MakeUppercase\contentsname}}%
\begin{flushright}
\textbf{หน้า}
\end{flushright}

% Counter for tocitem
\newcounter{tocitem}
\setcounter{tocitem}{0}

% Turn on the linenumber counts, but do not want to display it.
\modulolinenumbers[50]
\linenumbers[1]
\@starttoc{toc}%
\setcounter{tocitem}{0}
\clearpage
}

สร้างคำสั่งใหม่สำหรับตรวจสอบว่าบรรทัดปัจจุบัน หรือ จำนวนหัวข้อที่แสดงในสารบัญเกินค่าทึ่กำหนดหรือยัง หากเกินแล้วให้ตัดขึ้นหน้าใหม่ รีเซตตัวนับ tocitem รีเซตตัวนับบรรทัดใหม่ และเขียนหัวของหน้าสารบัญใหม่ (เช่น สารบัญ (ต่อ) ในกรณีนี้)
\newcommand{\tocheadcont}[3]{
\ifthenelse{#1>32 \OR #2>32}{
%Should be a newpage here.
\pagebreak[4]
\resetlinenumber[1]
\setcounter{tocitem}{0}
\begin{ċenter}
{\Large \textbf{#3 (ต่อ)} }
\end{center} 
\begin{flushright}
\textbf{หน้า}
\end{flushright}
}{}
}

การจัดรายการสารบัญโดยใช้คำสั่ง \titlecontents จากแพคเกจ titletoc
% tocitem format for chapter
\titlecontents{chapter}[0mm]
{\stepcounter{tocitem}\tocheadcont{\thelinenumber}{\thetocitem}{สารบัญ}}
{\chaptername\hspace{1ex}\thecontentslabel\hspace{2mm}}
{}{\hfill \contentspage}

% tocitem format for section
\titlecontents{section}[0mm]
{\stepcounter{tocitem}\tocheadcont{\thelinenumber}{\thetocitem}{สารบัญ}}
{\hspace{\firstindentlength}\thecontentslabel\hspace{2mm}}{\hspace{\firstindentlength}\thecontentslabel\hspace{2mm}}
{\hfill \contentspage}

ผลลัพธ์ที่ได้แสดงในรูป


กล่าวโดยสรุปเราใช้
  • \titlecontents จากแพคเกจ titletoc เพื่อจัดรูปแบบของรายการสารบัญ
  • \thelinenumber จากแพคเกจ lineno เพื่อนับบรรทัดในหน้า
  • \newcounter{tocitem} สร้างตัวนับรายการสารบัญ
  • \ifthenelse จากแพคเกจ ifthen เพื่อตรวจเงื่อนไข
อันที่จริงเราอาจใช้ทางอื่นในการสร้างหัวสารบัญในหน้าถัดไปได้ เช่นเอาหัวสารบัญไปใส่ในส่วนหัวของหน้าแล้วจัด Margin ใหม่ก็ได้ แต่ผมไม่ได้คิดถึงทางออกนี้จนกระทั่งแก้ปัญหามาทางนี้เกือบเสร็จแล้ว หากใครมีทางเลือกอื่น ๆ เพื่อจัดหน้าสารบัญสำหรับวิทยานิพนธ์ ม.ข. ก็จะขอแบ่งประสบการณ์ด้วยครับ