minmad: ignore "n%" when n > 100
[minmad.git] / minmad.c
blob0c2c512b00c28bfab934f139254f144562011a1f
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 <signal.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <sys/stat.h>
16 #include <sys/mman.h>
17 #include <sys/poll.h>
18 #include <termios.h>
19 #include <unistd.h>
20 #include <sys/soundcard.h>
21 #include <mad.h>
23 #define CTRLKEY(x) ((x) - 96)
24 #define MIN(a, b) ((a) < (b) ? (a) : (b))
25 #define MAX(a, b) ((a) > (b) ? (a) : (b))
26 #define JMP3 600
27 #define JMP2 60
28 #define JMP1 10
30 static struct mad_decoder decoder;
31 static struct termios termios;
32 static int afd; /* oss fd */
34 static char filename[1 << 12];
35 static unsigned char *mem;
36 static long len;
37 static long pos;
38 static int frame_sz; /* frame size */
39 static int frame_ms; /* frame duration in milliseconds */
40 static int played; /* playing time in milliseconds */
41 static int rate;
43 static int exited;
44 static int paused;
45 static int count;
47 static int oss_open(void)
49 afd = open("/dev/dsp", O_WRONLY);
50 return afd < 0;
53 static void oss_close(void)
55 if (afd > 0) /* zero fd is used for input */
56 close(afd);
57 afd = 0;
58 rate = 0;
61 static void oss_conf(void)
63 int ch = 2;
64 int bits = 16;
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 readkey(void)
72 char b;
73 if (read(0, &b, 1) <= 0)
74 return -1;
75 return b;
78 static void updatepos(void)
80 int sz, ms;
81 if (decoder.sync) {
82 pos = decoder.sync->stream.this_frame - mem;
83 sz = decoder.sync->stream.next_frame -
84 decoder.sync->stream.this_frame;
85 ms = mad_timer_count(decoder.sync->frame.header.duration,
86 MAD_UNITS_MILLISECONDS);
87 frame_ms = frame_ms ? ((frame_ms << 5) - frame_ms + ms) >> 5 : ms;
88 frame_sz = frame_sz ? ((frame_sz << 5) - frame_sz + sz) >> 5 : sz;
92 static void printinfo(void)
94 int per = pos * 1000.0 / len;
95 int loc = pos / frame_sz * frame_ms / 1000;
96 printf("%c %02d.%d%% (%d:%02d:%02d - %04d.%ds) [%s]\r",
97 paused ? (afd < 0 ? '*' : ' ') : '>',
98 per / 10, per % 10,
99 loc / 3600, (loc % 3600) / 60, loc % 60,
100 played / 1000, (played / 100) % 10,
101 filename);
102 fflush(stdout);
105 static int getcount(int def)
107 int result = count ? count : def;
108 count = 0;
109 return result;
112 static void seek(int n)
114 int diff = n * frame_sz * 1000 / (frame_ms ? frame_ms : 40);
115 pos = MAX(0, MIN(len, pos + diff));
118 static void seek_thousands(int n)
120 if (n <= 1000) {
121 pos = len * n / 1000;
122 pos -= pos % frame_sz;
126 static int execkey(void)
128 int c;
129 updatepos();
130 while ((c = readkey()) != -1) {
131 switch (c) {
132 case 'J':
133 seek(JMP3 * getcount(1));
134 return 1;
135 case 'K':
136 seek(-JMP3 * getcount(1));
137 return 1;
138 case 'j':
139 seek(JMP2 * getcount(1));
140 return 1;
141 case 'k':
142 seek(-JMP2 * getcount(1));
143 return 1;
144 case 'l':
145 seek(JMP1 * getcount(1));
146 return 1;
147 case 'h':
148 seek(-JMP1 * getcount(1));
149 return 1;
150 case '%':
151 seek_thousands(getcount(0) * 10);
152 return 1;
153 case 'i':
154 printinfo();
155 break;
156 case 'p':
157 case ' ':
158 if (paused)
159 if (oss_open())
160 break;
161 if (!paused)
162 oss_close();
163 paused = !paused;
164 return 1;
165 case 'q':
166 exited = 1;
167 return 1;
168 case 27:
169 count = 0;
170 break;
171 default:
172 if (isdigit(c))
173 count = count * 10 + c - '0';
176 return 0;
179 static enum mad_flow input(void *data, struct mad_stream *stream)
181 static unsigned long cpos;
182 if (pos && pos == cpos) {
183 exited = 1;
184 return MAD_FLOW_STOP;
186 cpos = pos;
187 mad_stream_buffer(stream, mem + pos, len - pos);
188 return MAD_FLOW_CONTINUE;
191 static signed int scale(mad_fixed_t sample)
193 /* round */
194 sample += (1L << (MAD_F_FRACBITS - 16));
195 /* clip */
196 if (sample >= MAD_F_ONE)
197 sample = MAD_F_ONE - 1;
198 else if (sample < -MAD_F_ONE)
199 sample = -MAD_F_ONE;
200 /* quantize */
201 return sample >> (MAD_F_FRACBITS + 1 - 16);
204 static void push_sample(char *buf, mad_fixed_t sample)
206 *buf++ = (sample >> 0) & 0xff;
207 *buf++ = (sample >> 8) & 0xff;
210 static char mixed[1 << 20];
211 static enum mad_flow output(void *data,
212 struct mad_header const *header,
213 struct mad_pcm *pcm)
215 int i;
216 int right = pcm->channels > 1 ? 1 : 0;
217 played += mad_timer_count(decoder.sync->frame.header.duration,
218 MAD_UNITS_MILLISECONDS);
219 for (i = 0; i < pcm->length; i++) {
220 push_sample(mixed + i * 4, scale(pcm->samples[0][i]));
221 push_sample(mixed + i * 4 + 2, scale(pcm->samples[right][i]));
223 if (header->samplerate != rate) {
224 rate = header->samplerate;
225 oss_conf();
227 write(afd, mixed, pcm->length * 4);
228 return execkey() ? MAD_FLOW_STOP : MAD_FLOW_CONTINUE;
231 static enum mad_flow error(void *data,
232 struct mad_stream *stream,
233 struct mad_frame *frame)
235 return MAD_FLOW_CONTINUE;
238 static void waitkey(void)
240 struct pollfd ufds[1];
241 ufds[0].fd = 0;
242 ufds[0].events = POLLIN;
243 poll(ufds, 1, -1);
246 static void decode(void)
248 mad_decoder_init(&decoder, NULL, input, 0, 0, output, error, 0);
249 while (!exited) {
250 if (paused) {
251 waitkey();
252 execkey();
253 } else {
254 mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
257 mad_decoder_finish(&decoder);
260 static void term_setup(void)
262 struct termios newtermios;
263 tcgetattr(0, &termios);
264 newtermios = termios;
265 newtermios.c_lflag &= ~ICANON;
266 newtermios.c_lflag &= ~ECHO;
267 tcsetattr(0, TCSAFLUSH, &newtermios);
268 fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
271 static void term_cleanup(void)
273 tcsetattr(0, 0, &termios);
276 static void sigcont(int sig)
278 term_setup();
281 int main(int argc, char *argv[])
283 struct stat stat;
284 int fd;
285 if (argc < 2)
286 return 1;
287 fd = open(argv[1], O_RDONLY);
288 strcpy(filename, argv[1]);
289 filename[30] = '\0';
290 if (fstat(fd, &stat) == -1 || stat.st_size == 0)
291 return 1;
292 mem = mmap(0, stat.st_size, PROT_READ, MAP_SHARED, fd, 0);
293 len = stat.st_size;
294 if (mem == MAP_FAILED)
295 return 1;
296 if (oss_open()) {
297 fprintf(stderr, "minmad: /dev/dsp busy?\n");
298 return 1;
300 term_setup();
301 signal(SIGCONT, sigcont);
302 decode();
303 oss_close();
304 term_cleanup();
305 munmap(mem, stat.st_size);
306 close(fd);
307 printf("\n");
308 return 0;