minmad: put marks with m and jump with '
[minmad.git] / minmad.c
blobb2897e9f04998d03359c0f78e6e2f9886c7a4748
1 /*
2 * minmad - a minimal mp3 player using libmad and oss
4 * Copyright (C) 2009-2015 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;
41 static int exited;
42 static int paused;
43 static int domark;
44 static int dojump;
45 static int doseek;
46 static int count;
48 static int oss_open(void)
50 afd = open("/dev/dsp", O_WRONLY);
51 return afd < 0;
54 static void oss_close(void)
56 if (afd > 0) /* zero fd is used for input */
57 close(afd);
58 afd = 0;
59 rate = 0;
62 static void oss_conf(int rate, int ch, int bits)
64 ioctl(afd, SOUND_PCM_WRITE_CHANNELS, &ch);
65 ioctl(afd, SOUND_PCM_WRITE_BITS, &bits);
66 ioctl(afd, SOUND_PCM_WRITE_RATE, &rate);
69 static int cmdread(void)
71 char b;
72 if (read(0, &b, 1) <= 0)
73 return -1;
74 return (unsigned char) b;
77 static void cmdwait(void)
79 struct pollfd ufds[1];
80 ufds[0].fd = 0;
81 ufds[0].events = POLLIN;
82 poll(ufds, 1, -1);
85 static void cmdinfo(void)
87 int per = (mpos + moff) * 1000 / msize;
88 int loc = (mpos + moff) / frame_sz * frame_ms / 1000;
89 printf("%c %02d.%d%% (%d:%02d:%02d - %04d.%ds) [%s]\r",
90 paused ? (afd < 0 ? '*' : ' ') : '>',
91 per / 10, per % 10,
92 loc / 3600, (loc % 3600) / 60, loc % 60,
93 played / 1000, (played / 100) % 10,
94 filename);
95 fflush(stdout);
98 static int cmdcount(int def)
100 int result = count ? count : def;
101 count = 0;
102 return result;
105 static void seek(long pos)
107 mark['\''] = mpos + moff;
108 mpos = MAX(0, MIN(msize, pos));
109 doseek = 1;
112 static void cmdseekrel(int n)
114 int diff = n * frame_sz * 1000 / (frame_ms ? frame_ms : 40);
115 seek(mpos + moff + diff);
118 static void cmdseek100(int n)
120 if (n <= 100)
121 seek(msize * n / 100);
124 static int cmdexec(void)
126 int c;
127 while ((c = cmdread()) >= 0) {
128 if (domark) {
129 domark = 0;
130 mark[c] = mpos + moff;
131 return 0;
133 if (dojump) {
134 dojump = 0;
135 if (mark[c] > 0)
136 seek(mark[c]);
137 return mark[c] > 0;
139 switch (c) {
140 case 'J':
141 cmdseekrel(+600 * cmdcount(1));
142 return 1;
143 case 'K':
144 cmdseekrel(-600 * cmdcount(1));
145 return 1;
146 case 'j':
147 cmdseekrel(+60 * cmdcount(1));
148 return 1;
149 case 'k':
150 cmdseekrel(-60 * cmdcount(1));
151 return 1;
152 case 'l':
153 cmdseekrel(+10 * cmdcount(1));
154 return 1;
155 case 'h':
156 cmdseekrel(-10 * cmdcount(1));
157 return 1;
158 case '%':
159 cmdseek100(cmdcount(0));
160 return 1;
161 case 'i':
162 cmdinfo();
163 break;
164 case 'm':
165 domark = 1;
166 break;
167 case '\'':
168 dojump = 1;
169 break;
170 case 'p':
171 case ' ':
172 if (paused)
173 if (oss_open())
174 break;
175 if (!paused)
176 oss_close();
177 paused = !paused;
178 return 1;
179 case 'q':
180 exited = 1;
181 return 1;
182 case 27:
183 count = 0;
184 break;
185 default:
186 if (isdigit(c))
187 count = count * 10 + c - '0';
190 return 0;
193 static enum mad_flow madinput(void *data, struct mad_stream *stream)
195 int nread = stream->next_frame ? stream->next_frame - mbuf : 0;
196 int nleft = nread ? mlen - nread : 0;
197 int nr;
198 if (doseek) {
199 doseek = 0;
200 nleft = 0;
201 nread = 0;
202 lseek(mfd, mpos, 0);
204 memmove(mbuf, mbuf + nread, nleft);
205 if ((nr = read(mfd, mbuf + nleft, sizeof(mbuf) - nleft)) <= 0) {
206 exited = 1;
207 return MAD_FLOW_STOP;
209 mlen = nleft + nr;
210 mad_stream_buffer(stream, mbuf, mlen);
211 mpos += nread;
212 moff = 0;
213 return MAD_FLOW_CONTINUE;
216 static signed int madscale(mad_fixed_t sample)
218 sample += (1l << (MAD_F_FRACBITS - 16)); /* round */
219 if (sample >= MAD_F_ONE) /* clip */
220 sample = MAD_F_ONE - 1;
221 if (sample < -MAD_F_ONE)
222 sample = -MAD_F_ONE;
223 return sample >> (MAD_F_FRACBITS + 1 - 16); /* quantize */
226 static void madupdate(void)
228 int sz, ms;
229 if (maddec.sync) {
230 moff = maddec.sync->stream.this_frame - mbuf;
231 sz = maddec.sync->stream.next_frame -
232 maddec.sync->stream.this_frame;
233 ms = mad_timer_count(maddec.sync->frame.header.duration,
234 MAD_UNITS_MILLISECONDS);
235 frame_ms = frame_ms ? ((frame_ms << 5) - frame_ms + ms) >> 5 : ms;
236 frame_sz = frame_sz ? ((frame_sz << 5) - frame_sz + sz) >> 5 : sz;
240 static char mixed[1 << 18];
241 static enum mad_flow madoutput(void *data,
242 struct mad_header const *header,
243 struct mad_pcm *pcm)
245 int c1 = 0;
246 int c2 = pcm->channels > 1 ? 1 : 0;
247 int i;
248 played += mad_timer_count(maddec.sync->frame.header.duration,
249 MAD_UNITS_MILLISECONDS);
250 for (i = 0; i < pcm->length; i++) {
251 mixed[i * 4 + 0] = madscale(pcm->samples[c1][i]) & 0xff;
252 mixed[i * 4 + 1] = (madscale(pcm->samples[c1][i]) >> 8) & 0xff;
253 mixed[i * 4 + 2] = madscale(pcm->samples[c2][i]) & 0xff;
254 mixed[i * 4 + 3] = (madscale(pcm->samples[c2][i]) >> 8) & 0xff;
256 if (header->samplerate != rate) {
257 rate = header->samplerate;
258 oss_conf(rate, 2, 16);
260 write(afd, mixed, pcm->length * 4);
261 madupdate();
262 return cmdexec() ? MAD_FLOW_STOP : MAD_FLOW_CONTINUE;
265 static enum mad_flow maderror(void *data,
266 struct mad_stream *stream,
267 struct mad_frame *frame)
269 return MAD_FLOW_CONTINUE;
272 static void maddecode(void)
274 mad_decoder_init(&maddec, NULL, madinput, 0, 0, madoutput, maderror, 0);
275 while (!exited) {
276 if (paused) {
277 cmdwait();
278 cmdexec();
279 } else {
280 mad_decoder_run(&maddec, MAD_DECODER_MODE_SYNC);
283 mad_decoder_finish(&maddec);
286 static void term_init(struct termios *termios)
288 struct termios newtermios;
289 tcgetattr(0, termios);
290 newtermios = *termios;
291 newtermios.c_lflag &= ~ICANON;
292 newtermios.c_lflag &= ~ECHO;
293 tcsetattr(0, TCSAFLUSH, &newtermios);
294 fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
297 static void term_done(struct termios *termios)
299 tcsetattr(0, 0, termios);
302 int main(int argc, char *argv[])
304 struct stat stat;
305 struct termios termios;
306 if (argc < 2)
307 return 1;
308 snprintf(filename, 30, "%s", argv[1]);
309 mfd = open(argv[1], O_RDONLY);
310 if (fstat(mfd, &stat) == -1 || stat.st_size == 0)
311 return 1;
312 msize = stat.st_size;
313 if (oss_open()) {
314 fprintf(stderr, "minmad: /dev/dsp busy?\n");
315 return 1;
317 term_init(&termios);
318 maddecode();
319 oss_close();
320 term_done(&termios);
321 close(mfd);
322 printf("\n");
323 return 0;