sq3: show SQLite error messages on stderr by default
[iv.d.git] / follin / samples / music_player.d
blobc49874231ef2f001541af9f50f10de30a9d0ba5f
1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module music_player /*is aliced*/;
19 import core.atomic;
21 import iv.alice;
22 import iv.follin;
23 import iv.rawtty;
24 import iv.encoding;
26 import iv.drflac;
27 import iv.stb.vorbis;
30 // ////////////////////////////////////////////////////////////////////////// //
31 string[] playlist;
32 usize plidx;
35 // ////////////////////////////////////////////////////////////////////////// //
36 __gshared uint vrTotalTimeMsec = 0;
37 __gshared uint vrNextTimeMsec = 0;
38 __gshared bool vrPaused = false;
39 __gshared ubyte vrVolume = 255;
40 __gshared bool forceUp = false;
43 void showProgress (bool endtime=false) {
44 auto curTime = (endtime ? vrTotalTimeMsec : tflChannelPlayTimeMsec("music"));
45 if (curTime >= vrNextTimeMsec || tflPaused != vrPaused || forceUp) {
46 vrNextTimeMsec = curTime+1000;
47 vrPaused = tflPaused;
48 import core.sys.posix.unistd : write;
49 import std.string : format;
50 auto pstr = "\r%02s:%02s/%02s:%02s %3s%%%s\e[K".format(curTime/1000/60, curTime/1000%60, vrTotalTimeMsec/1000/60, vrTotalTimeMsec/1000%60,
51 100*vrVolume/255, (vrPaused ? " [P]" : ""));
52 write(1/*stdout*/, pstr.ptr, cast(uint)pstr.length);
53 forceUp = false;
58 long totalTimeMsec (FileChannel chan) {
59 auto res = chan.totalMsecs;
60 return (res >= 0 ? res : 0);
64 int quality = TflChannel.QualityMusic;
66 enum Action { Quit, Prev, Next }
68 Action playFile () {
69 if (plidx >= playlist.length) return Action.Quit;
71 Action res = Action.Next;
72 FileChannel chan = FileChannel.detect(playlist[plidx]);
74 if (chan is null) {
75 foreach (immutable c; plidx+1..playlist.length) playlist[c-1] = playlist[c];
76 playlist.length -= 1;
77 return Action.Prev;
81 import core.stdc.stdio;
82 import std.path : baseName;
83 auto bn = playlist[plidx].baseName;
84 printf("=== [%u/%u] %.*s (%d) ===\n", cast(uint)(plidx+1), cast(uint)playlist.length, cast(uint)bn.length, bn.ptr, quality);
87 if (auto vc = cast(VorbisChannel)chan.srcchan) {
88 for (;;) {
89 import std.stdio;
90 auto name = vc.vf.comment_name;
91 auto value = vc.vf.comment_value;
92 if (name is null) break;
93 if (utf8Valid(value)) value = recodeToKOI8(value);
94 writeln(" ", name, "=", value);
95 vc.vf.comment_skip();
97 } else if (auto fc = cast(FlacChannel)chan.srcchan) {
98 foreach (string val; fc.comments) {
99 import std.stdio;
100 if (utf8Valid(val)) val = recodeToKOI8(val);
101 writeln(" ", val);
105 vrTotalTimeMsec = cast(uint)chan.totalTimeMsec;
106 vrNextTimeMsec = 0;
108 if (!tflAddChannel("music", chan, TFLmusic, quality)) {
109 import core.stdc.stdio;
110 fprintf(stderr, "ERROR: can't add ogg channel!\n");
111 return Action.Quit;
114 while (tflIsChannelAlive("music")) {
115 showProgress();
116 // process keys
117 if (ttyWaitKey(200)) {
118 auto key = ttyReadKey();
119 if (key.key != TtyEvent.Key.Char) continue;
120 if (key.ch > 127) continue;
121 switch (cast(char)key.ch) {
122 case ' ':
123 tflPaused = !tflPaused;
124 break;
125 case 'p':
126 if (auto oc = tflChannelObject("music")) {
127 auto p = !oc.paused;
128 oc.paused = p;
130 break;
131 case 'q':
132 tflKillChannel("music");
133 res = Action.Quit;
134 break;
135 case '<':
136 tflKillChannel("music");
137 res = Action.Prev;
138 break;
139 case '>':
140 tflKillChannel("music");
141 res = Action.Next;
142 break;
143 case '9':
144 if (vrVolume > 0) {
145 --vrVolume;
146 chan.volume = vrVolume;
147 forceUp = true;
149 break;
150 case '0':
151 if (vrVolume < 255) {
152 ++vrVolume;
153 chan.volume = vrVolume;
154 forceUp = true;
156 break;
157 default:
161 assert(!tflIsChannelAlive("music"));
162 forceUp = true;
163 showProgress(true);
164 { import core.stdc.stdio; printf("\n"); }
165 // sleep 100 ms
166 //foreach (immutable _; 0..100) { import core.sys.posix.unistd : usleep; usleep(1000); }
167 return res;
171 // ////////////////////////////////////////////////////////////////////////// //
172 void main (string[] args) {
173 if (ttyIsRedirected) throw new Exception("no redirects, please!");
175 if (args.length < 2) throw new Exception("filename?");
177 bool nomoreargs = false;
178 bool shuffle = false;
180 foreach (string arg; args[1..$]) {
181 if (args.length == 0) continue;
182 if (!nomoreargs) {
183 if (arg == "--") { nomoreargs = true; continue; }
184 if (arg == "--cubic") { quality = -1; continue; }
185 if (arg == "--best") { quality = 10; continue; }
186 if (arg == "--shuffle") { shuffle = true; continue; }
187 if (arg[0] == '-') {
188 if (arg.length == 2 && arg[1] >= '0' && arg[1] <= '9') { quality = arg[1]-'0'; continue; }
189 if (arg.length == 3 && arg[1] == 'q' && arg[2] >= '0' && arg[2] <= '9') { quality = arg[2]-'0'; continue; }
190 throw new Exception("unknown option: '"~arg~"'");
193 import std.file : exists;
194 if (!arg.exists) {
195 import core.stdc.stdio;
196 printf("skipped '%.*s'\n", cast(uint)arg.length, arg.ptr);
197 } else {
198 playlist ~= arg;
202 if (playlist.length == 0) throw new Exception("no files!");
203 if (shuffle) {
204 import std.random;
205 playlist.randomShuffle;
208 ttySetRaw();
209 scope(exit) ttySetNormal();
211 tflInit();
212 scope(exit) tflDeinit();
213 { import std.stdio; writeln("latency: ", tflLatency); }
215 mainloop: for (;;) {
216 final switch (playFile()) with (Action) {
217 case Prev: if (plidx > 0) --plidx; break;
218 case Next: ++plidx; break;
219 case Quit: break mainloop;