README: describe available commands
[minmad.git] / minmad.c
blob33c9870dde77bfd9792f0120698e3ad8d7d40087
1 /*
2 * minmad - a minimal mp3 player using libmad and oss
4 * Copyright (C) 2009-2016 Ali Gholami Rudi
6 * This program is released under the Modified BSD license.
7 */
8 #include <ctype.h>
9 #include <fcntl.h>
10 #include <pty.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <sys/stat.h>
15 #include <sys/poll.h>
16 #include <termios.h>
17 #include <unistd.h>
18 #include <sys/soundcard.h>
19 #include <mad.h>
21 #define CTRLKEY(x) ((x) - 96)
22 #define MIN(a, b) ((a) < (b) ? (a) : (b))
23 #define MAX(a, b) ((a) > (b) ? (a) : (b))
25 static struct mad_decoder maddec;
26 static int afd; /* oss fd */
28 static char filename[128];
29 static int mfd; /* input file descriptor */
30 static long msize; /* file size */
31 static unsigned char mbuf[1 << 16];
32 static long mpos; /* the position of mbuf[] */
33 static long mlen; /* data in mbuf[] */
34 static long moff; /* offset into mbuf[] */
35 static long mark[256]; /* mark positions */
36 static int frame_sz; /* frame size */
37 static int frame_ms; /* frame duration in milliseconds */
38 static int played; /* playing time in milliseconds */
39 static int rate; /* current oss sample rate */
40 static int topause; /* planned pause (compared with played) */
42 static int exited;
43 static int paused;
44 static int domark;
45 static int dojump;
46 static int doseek;
47 static int count;
49 static int oss_open(void)
51 afd = open("/dev/dsp", O_WRONLY);
52 return afd < 0;
55 static void oss_close(void)
57 if (afd > 0) /* zero fd is used for input */
58 close(afd);
59 afd = 0;
60 rate = 0;
63 static void oss_conf(int rate, int ch, int bits)
65 ioctl(afd, SOUND_PCM_WRITE_CHANNELS, &ch);
66 ioctl(afd, SOUND_PCM_WRITE_BITS, &bits);
67 ioctl(afd, SOUND_PCM_WRITE_RATE, &rate);
70 static int cmdread(void)
72 char b;
73 if (read(0, &b, 1) <= 0)
74 return -1;
75 return (unsigned char) b;
78 static void cmdwait(void)
80 struct pollfd ufds[1];
81 ufds[0].fd = 0;
82 ufds[0].events = POLLIN;
83 poll(ufds, 1, -1);
86 static long muldiv64(long num, long mul, long div)
88 return (long long) num * mul / div;
91 static void cmdinfo(void)
93 int per = muldiv64(mpos + moff, 1000, msize);
94 int loc = muldiv64(mpos + moff, frame_ms, frame_sz * 1000);
95 printf("%c %02d.%d%% (%d:%02d:%02d - %04d.%ds) [%s]\r",
96 paused ? (afd < 0 ? '*' : ' ') : '>',
97 per / 10, per % 10,
98 loc / 3600, (loc % 3600) / 60, loc % 60,
99 played / 1000, (played / 100) % 10,
100 filename);
101 fflush(stdout);
104 static int cmdcount(int def)
106 int result = count ? count : def;
107 count = 0;
108 return result;
111 static void seek(long pos)
113 mark['\''] = mpos + moff;
114 mpos = MAX(0, MIN(msize, pos));
115 doseek = 1;
118 static void cmdseekrel(int n)
120 int diff = muldiv64(n, frame_sz * 1000, frame_ms ? frame_ms : 40);
121 seek(mpos + moff + diff);
124 static void cmdseek100(int n)
126 if (n <= 100)
127 seek(muldiv64(msize, n, 100));
130 static int cmdpause(int pause)
132 if (!pause && paused) {
133 if (oss_open())
134 return 1;
135 paused = 0;
137 if (pause && !paused) {
138 oss_close();
139 paused = 1;
141 return 0;
144 static int cmdexec(void)
146 int c;
147 if (topause > 0 && topause <= played) {
148 topause = 0;
149 return !cmdpause(1);
151 while ((c = cmdread()) >= 0) {
152 if (domark) {
153 domark = 0;
154 mark[c] = mpos + moff;
155 return 0;
157 if (dojump) {
158 dojump = 0;
159 if (mark[c] > 0)
160 seek(mark[c]);
161 return mark[c] > 0;
163 switch (c) {
164 case 'J':
165 cmdseekrel(+600 * cmdcount(1));
166 return 1;
167 case 'K':
168 cmdseekrel(-600 * cmdcount(1));
169 return 1;
170 case 'j':
171 cmdseekrel(+60 * cmdcount(1));
172 return 1;
173 case 'k':
174 cmdseekrel(-60 * cmdcount(1));
175 return 1;
176 case 'l':
177 cmdseekrel(+10 * cmdcount(1));
178 return 1;
179 case 'h':
180 cmdseekrel(-10 * cmdcount(1));
181 return 1;
182 case '%':
183 cmdseek100(cmdcount(0));
184 return 1;
185 case 'i':
186 cmdinfo();
187 break;
188 case 'm':
189 domark = 1;
190 break;
191 case '\'':
192 dojump = 1;
193 break;
194 case 'p':
195 case ' ':
196 if (cmdpause(!paused))
197 break;
198 return 1;
199 case 'P':
200 topause = count ? played + cmdcount(0) * 60000 : 0;
201 break;
202 case 'q':
203 exited = 1;
204 return 1;
205 case 27:
206 count = 0;
207 break;
208 default:
209 if (isdigit(c))
210 count = count * 10 + c - '0';
213 return 0;
216 static enum mad_flow madinput(void *data, struct mad_stream *stream)
218 int nread = stream->next_frame ? stream->next_frame - mbuf : moff;
219 int nleft = mlen - nread;
220 int nr = 0;
221 if (doseek) {
222 doseek = 0;
223 nleft = 0;
224 nread = 0;
225 lseek(mfd, mpos, 0);
227 memmove(mbuf, mbuf + nread, nleft);
228 if (nleft < sizeof(mbuf)) {
229 if ((nr = read(mfd, mbuf + nleft, sizeof(mbuf) - nleft)) <= 0) {
230 exited = 1;
231 return MAD_FLOW_STOP;
234 mlen = nleft + nr;
235 mad_stream_buffer(stream, mbuf, mlen);
236 mpos += nread;
237 moff = 0;
238 return MAD_FLOW_CONTINUE;
241 static signed int madscale(mad_fixed_t sample)
243 sample += (1l << (MAD_F_FRACBITS - 16)); /* round */
244 if (sample >= MAD_F_ONE) /* clip */
245 sample = MAD_F_ONE - 1;
246 if (sample < -MAD_F_ONE)
247 sample = -MAD_F_ONE;
248 return sample >> (MAD_F_FRACBITS + 1 - 16); /* quantize */
251 static void madupdate(void)
253 int sz, ms;
254 if (maddec.sync) {
255 moff = maddec.sync->stream.this_frame - mbuf;
256 sz = maddec.sync->stream.next_frame -
257 maddec.sync->stream.this_frame;
258 ms = mad_timer_count(maddec.sync->frame.header.duration,
259 MAD_UNITS_MILLISECONDS);
260 frame_ms = frame_ms ? ((frame_ms << 5) - frame_ms + ms) >> 5 : ms;
261 frame_sz = frame_sz ? ((frame_sz << 5) - frame_sz + sz) >> 5 : sz;
265 static char mixed[1 << 18];
266 static enum mad_flow madoutput(void *data,
267 struct mad_header const *header,
268 struct mad_pcm *pcm)
270 int c1 = 0;
271 int c2 = pcm->channels > 1 ? 1 : 0;
272 int i;
273 played += mad_timer_count(maddec.sync->frame.header.duration,
274 MAD_UNITS_MILLISECONDS);
275 for (i = 0; i < pcm->length; i++) {
276 mixed[i * 4 + 0] = madscale(pcm->samples[c1][i]) & 0xff;
277 mixed[i * 4 + 1] = (madscale(pcm->samples[c1][i]) >> 8) & 0xff;
278 mixed[i * 4 + 2] = madscale(pcm->samples[c2][i]) & 0xff;
279 mixed[i * 4 + 3] = (madscale(pcm->samples[c2][i]) >> 8) & 0xff;
281 if (header->samplerate != rate) {
282 rate = header->samplerate;
283 oss_conf(rate, 2, 16);
285 write(afd, mixed, pcm->length * 4);
286 madupdate();
287 return cmdexec() ? MAD_FLOW_STOP : MAD_FLOW_CONTINUE;
290 static enum mad_flow maderror(void *data,
291 struct mad_stream *stream,
292 struct mad_frame *frame)
294 return MAD_FLOW_CONTINUE;
297 static void maddecode(void)
299 mad_decoder_init(&maddec, NULL, madinput, 0, 0, madoutput, maderror, 0);
300 while (!exited) {
301 if (paused) {
302 cmdwait();
303 cmdexec();
304 } else {
305 mad_decoder_run(&maddec, MAD_DECODER_MODE_SYNC);
308 mad_decoder_finish(&maddec);
311 static void term_init(struct termios *termios)
313 struct termios newtermios;
314 tcgetattr(0, termios);
315 newtermios = *termios;
316 newtermios.c_lflag &= ~ICANON;
317 newtermios.c_lflag &= ~ECHO;
318 tcsetattr(0, TCSAFLUSH, &newtermios);
319 fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
322 static void term_done(struct termios *termios)
324 tcsetattr(0, 0, termios);
327 int main(int argc, char *argv[])
329 struct stat stat;
330 struct termios termios;
331 char *path = argc >= 2 ? argv[1] : NULL;
332 if (!path)
333 return 1;
334 if (strchr(path, '/'))
335 path = strrchr(path, '/') + 1;
336 snprintf(filename, 30, "%s", path);
337 mfd = open(argv[1], O_RDONLY);
338 if (fstat(mfd, &stat) == -1 || stat.st_size == 0)
339 return 1;
340 msize = stat.st_size;
341 if (oss_open()) {
342 fprintf(stderr, "minmad: /dev/dsp busy?\n");
343 return 1;
345 term_init(&termios);
346 maddecode();
347 oss_close();
348 term_done(&termios);
349 close(mfd);
350 printf("\n");
351 return 0;