Skip to content

Commit 5f6b2fd

Browse files
committed
basic mod player code
1 parent 662c0a7 commit 5f6b2fd

File tree

4 files changed

+343
-2
lines changed

4 files changed

+343
-2
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ xcuserdata
66
xuserdata
77
profile
88
build/
9+
mods/
10+
ft/
911

1012
udp-flaschen-taschen.o
13+
ft-mod
1114

Makefile

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
FLASCHEN_TASCHEN_API_DIR = ft/api
2+
3+
CXXFLAGS = -Wall -O3 -I$(FLASCHEN_TASCHEN_API_DIR)/include -I.
4+
LDFLAGS = -L$(FLASCHEN_TASCHEN_API_DIR)/lib -lftclient
5+
FTLIB = $(FLASCHEN_TASCHEN_API_DIR)/lib/libftclient.a
6+
7+
## test ##
8+
# CPPFLAGS_OPENMPT123 += $(CPPFLAGS_SDL2) $(CPPFLAGS_SDL) $(CPPFLAGS_PORTAUDIO) $(CPPFLAGS_PULSEAUDIO) $(CPPFLAGS_FLAC) $(CPPFLAGS_SNDFILE)
9+
# LDFLAGS_OPENMPT123 += $(LDFLAGS_SDL2) $(LDFLAGS_SDL) $(LDFLAGS_PORTAUDIO) $(LDFLAGS_PULSEAUDIO) $(LDFLAGS_FLAC) $(LDFLAGS_SNDFILE)
10+
# LDLIBS_OPENMPT123 += $(LDLIBS_SDL2) $(LDLIBS_SDL) $(LDLIBS_PORTAUDIO) $(LDLIBS_PULSEAUDIO) $(LDLIBS_FLAC) $(LDLIBS_SNDFILE)
11+
# LDLIBS_LIBOPENMPT += -lopenmpt
12+
#LDLIBS += -lportaudio
13+
#LDLIBS += -lFLAC
14+
#LDLIBS += -lsndfile
15+
16+
LDFLAGS += -lportaudio -lopenmpt
17+
18+
ALL = ft-mod
19+
20+
all : $(ALL)
21+
22+
% : %.cc $(FTLIB)
23+
$(CXX) $(CXXFLAGS) -o $@ $< $(LDFLAGS)
24+
25+
$(FTLIB):
26+
make -C $(FLASCHEN_TASCHEN_API_DIR)/lib
27+
28+
clean:
29+
rm -f $(ALL)

README.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ This program is currently incomplete and not yet ready to use.
1010

1111
### How to Install
1212

13-
This project uses the C++ framework provided by the `flaschen-taschen` project.
14-
Since it's being used as a sub-module, you'll want to clone this repo with the `--recursive` option:
13+
This project uses the C++ framework provided by the [flaschen-taschen](https://github.com/hzeller/flaschen-taschen) project. Since it's being used as a sub-module, you'll want to clone this repo with the `--recursive` option:
1514

1615
```
1716
git clone --recursive https://github.com/cgorringe/ft-mod

ft-mod.cc

+310
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
// -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
2+
//
3+
// ft-mod
4+
// Copyright (c) 2017 Carl Gorringe (carl.gorringe.org)
5+
// https://github.com/cgorringe/ft-mod
6+
// 9/2/2017
7+
//
8+
// Plays & Displays MOD Music Modules.
9+
//
10+
// Don't have any? Download modules from modarchive.org
11+
//
12+
// How to run:
13+
//
14+
// export FT_DISPLAY=localhost
15+
// ./ft-mod ...
16+
//
17+
// To see command line options:
18+
// ./ft-mod -?
19+
//
20+
// --------------------------------------------------------------------------------
21+
//
22+
// This program is free software; you can redistribute it and/or modify
23+
// it under the terms of the GNU General Public License as published by
24+
// the Free Software Foundation version 2.
25+
//
26+
// This program is distributed in the hope that it will be useful,
27+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
28+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29+
// GNU General Public License for more details.
30+
//
31+
// You should have received a copy of the GNU General Public License
32+
// along with this program. If not, see <http://gnu.org/licenses/gpl-2.0.txt>
33+
//
34+
35+
#include "ft/api/include/udp-flaschen-taschen.h"
36+
37+
#include <getopt.h>
38+
#include <stdio.h>
39+
#include <unistd.h>
40+
#include <stdlib.h>
41+
#include <stdint.h>
42+
#include <limits.h>
43+
#include <time.h>
44+
#include <math.h>
45+
#include <string.h>
46+
#include <string>
47+
#include <signal.h>
48+
49+
//-- used by mod player --
50+
#include <memory.h>
51+
//#include <stdint.h>
52+
//#include <stdio.h>
53+
//#include <stdlib.h>
54+
//#include <string.h>
55+
#include <libopenmpt/libopenmpt.h>
56+
#include <libopenmpt/libopenmpt_stream_callbacks_file.h>
57+
// must have PortAudio installed
58+
// on Mac: brew install portaudio
59+
#include <portaudio.h>
60+
#define BUFFERSIZE 480
61+
#define SAMPLERATE 48000
62+
static int16_t audio_left[BUFFERSIZE];
63+
static int16_t audio_right[BUFFERSIZE];
64+
static int16_t * const audio_buffers[2] = { audio_left, audio_right };
65+
// ------
66+
67+
// Defaults large small
68+
#define DISPLAY_WIDTH (9*5) // 9*5 5*5
69+
#define DISPLAY_HEIGHT (7*5) // 7*5 4*5
70+
#define Z_LAYER 7 // (0-15) 0=background
71+
#define DELAY 25
72+
#define MAX_PATH_LENGTH 250
73+
74+
volatile bool interrupt_received = false;
75+
static void InterruptHandler(int signo) {
76+
interrupt_received = true;
77+
}
78+
79+
// ------------------------------------------------------------------------------------------
80+
// Command Line Options
81+
82+
// option vars
83+
const char *opt_hostname = NULL;
84+
int opt_layer = Z_LAYER;
85+
double opt_timeout = 60*60*24; // timeout in 24 hrs
86+
int opt_width = DISPLAY_WIDTH;
87+
int opt_height = DISPLAY_HEIGHT;
88+
int opt_xoff=0, opt_yoff=0;
89+
int opt_delay = DELAY;
90+
char opt_filepath[MAX_PATH_LENGTH];
91+
92+
int usage(const char *progname) {
93+
94+
fprintf(stderr, "ft-mod (c) 2017 Carl Gorringe (carl.gorringe.org)\n");
95+
fprintf(stderr, "Usage: %s [options] <mod-file>\n", progname);
96+
fprintf(stderr, "Options:\n"
97+
"\t-g <W>x<H>[+<X>+<Y>] : Output geometry. (default 45x35+0+0)\n"
98+
"\t-l <layer> : Layer 0-15. (default 7)\n"
99+
"\t-t <timeout> : Timeout exits after given seconds. (default 24hrs)\n"
100+
"\t-h <host> : Flaschen-Taschen display hostname. (FT_DISPLAY)\n"
101+
"\t-d <delay> : Delay between frames in milliseconds. (default 25)\n"
102+
);
103+
return 1;
104+
}
105+
106+
int cmdLine(int argc, char *argv[]) {
107+
108+
// command line options
109+
int opt;
110+
while ((opt = getopt(argc, argv, "?g:l:t:h:d:")) != -1) {
111+
switch (opt) {
112+
case '?': // help
113+
return usage(argv[0]);
114+
break;
115+
case 'g': // geometry
116+
if (sscanf(optarg, "%dx%d%d%d", &opt_width, &opt_height, &opt_xoff, &opt_yoff) < 2) {
117+
fprintf(stderr, "Invalid size '%s'\n", optarg);
118+
return usage(argv[0]);
119+
}
120+
break;
121+
case 'l': // layer
122+
if (sscanf(optarg, "%d", &opt_layer) != 1 || opt_layer < 0 || opt_layer >= 16) {
123+
fprintf(stderr, "Invalid layer '%s'\n", optarg);
124+
return usage(argv[0]);
125+
}
126+
break;
127+
case 't': // timeout
128+
if (sscanf(optarg, "%lf", &opt_timeout) != 1 || opt_timeout < 0) {
129+
fprintf(stderr, "Invalid timeout '%s'\n", optarg);
130+
return usage(argv[0]);
131+
}
132+
break;
133+
case 'h': // hostname
134+
opt_hostname = strdup(optarg); // leaking. Ignore.
135+
break;
136+
case 'd': // delay
137+
if (sscanf(optarg, "%d", &opt_delay) != 1 || opt_delay < 1) {
138+
fprintf(stderr, "Invalid delay '%s'\n", optarg);
139+
return usage(argv[0]);
140+
}
141+
break;
142+
default:
143+
return usage(argv[0]);
144+
}
145+
}
146+
147+
// assign default
148+
strncpy(opt_filepath, "", MAX_PATH_LENGTH);
149+
150+
// retrieve arg text from remaining arguments
151+
// TODO: convert this to store an array of filenames
152+
std::string str;
153+
if (argv[optind]) {
154+
str.append(argv[optind]);
155+
for (int i = optind + 1; i < argc; i++) {
156+
str.append(" ").append(argv[i]);
157+
}
158+
const char *text = str.c_str();
159+
while (isspace(*text)) { text++; } // remove leading spaces
160+
if (text && (strlen(text) > 0)) {
161+
strncpy(opt_filepath, text, MAX_PATH_LENGTH);
162+
}
163+
}
164+
165+
return 0;
166+
}
167+
168+
// ------------------------------------------------------------------------------------------
169+
170+
int main(int argc, char *argv[]) {
171+
172+
// parse command line
173+
if (int e = cmdLine(argc, argv)) { return e; }
174+
175+
srandom(time(NULL)); // seed the random generator
176+
177+
// open socket and create our canvas
178+
const int socket = OpenFlaschenTaschenSocket(opt_hostname);
179+
UDPFlaschenTaschen canvas(socket, opt_width, opt_height);
180+
canvas.Clear();
181+
182+
/*
183+
// pixel buffer
184+
uint8_t pixels[ opt_width * opt_height ];
185+
for (int i=0; i < opt_width * opt_height; i++) { pixels[i] = 0; } // clear pixel buffer
186+
187+
// color palette
188+
Color palette[256];
189+
int curPalette = (opt_palette < 0) ? 1 : opt_palette;
190+
setPalette(curPalette, palette);
191+
//*/
192+
193+
// handle break
194+
signal(SIGTERM, InterruptHandler);
195+
signal(SIGINT, InterruptHandler);
196+
197+
// other vars
198+
int count=0;
199+
time_t starttime = time(NULL);
200+
201+
// setup MOD player
202+
FILE *modfile = NULL;
203+
openmpt_module *mod = NULL;
204+
size_t mod_count = 0;
205+
PaError pa_error = paNoError;
206+
PaStream *audio_stream = NULL;
207+
208+
if (strlen(opt_filepath) > 0) {
209+
fprintf(stderr, "Playing: %s\n", opt_filepath);
210+
211+
modfile = fopen(opt_filepath, "rb");
212+
if (!modfile) {
213+
fprintf(stderr, "Error: Couldn't open file or wrong path.\n");
214+
return 1;
215+
}
216+
217+
mod = openmpt_module_create(openmpt_stream_get_file_callbacks(), modfile, NULL, NULL, NULL);
218+
if (!mod) {
219+
fprintf(stderr, "Error: Not a MOD file?\n");
220+
if (modfile) { fclose(modfile); }
221+
return 1;
222+
}
223+
224+
if (modfile) { fclose(modfile); }
225+
pa_error = Pa_Initialize();
226+
if (pa_error != paNoError) {
227+
fprintf(stderr, "Error: PortAudio init failed.\n");
228+
return 1;
229+
}
230+
231+
pa_error = Pa_OpenDefaultStream(&audio_stream, 0, 2, paInt16 | paNonInterleaved, SAMPLERATE,
232+
paFramesPerBufferUnspecified, NULL, NULL);
233+
if ( !((pa_error == paNoError) && audio_stream) ) {
234+
fprintf(stderr, "Error: PortAudio opening stream failed.\n");
235+
return 1;
236+
}
237+
238+
pa_error = Pa_StartStream(audio_stream);
239+
if (pa_error != paNoError) {
240+
fprintf(stderr, "Error: PortAudio starting stream failed.\n");
241+
return 1;
242+
}
243+
}
244+
else {
245+
fprintf(stderr, "Missing MOD file.\n");
246+
return usage(argv[0]);
247+
}
248+
249+
do {
250+
/*
251+
// copy pixel buffer to canvas
252+
int dst = 0;
253+
for (int y=0; y < opt_height; y++) {
254+
for (int x=0; x < opt_width; x++) {
255+
canvas.SetPixel( x, y, palette[ pixels[dst] ] );
256+
dst++;
257+
}
258+
}
259+
//*/
260+
261+
/*
262+
// send canvas
263+
canvas.SetOffset(opt_xoff, opt_yoff, opt_layer);
264+
canvas.Send();
265+
usleep(opt_delay * 1000);
266+
//*/
267+
268+
// play mod
269+
mod_count = openmpt_module_read_stereo(mod, SAMPLERATE, BUFFERSIZE, audio_left, audio_right);
270+
if (mod_count == 0) { break; }
271+
pa_error = Pa_WriteStream(audio_stream, audio_buffers, (unsigned long)mod_count);
272+
if (pa_error == paOutputUnderflowed) {
273+
// not an error
274+
}
275+
else if (pa_error != paNoError) {
276+
fprintf(stderr, "Error: Writing to stream failed.\n");
277+
interrupt_received = true;
278+
}
279+
280+
// print info
281+
fprintf(stderr, "%d \r", count);
282+
283+
count++;
284+
if (count == INT_MAX) { count=0; }
285+
286+
} while ( (difftime(time(NULL), starttime) <= opt_timeout) && !interrupt_received );
287+
288+
fprintf(stderr, "\nDone.\n");
289+
290+
// cleanup MOD player
291+
if (audio_stream) {
292+
if ( Pa_IsStreamActive(audio_stream) == 1 ) {
293+
Pa_StopStream(audio_stream);
294+
}
295+
Pa_CloseStream(audio_stream);
296+
audio_stream = NULL;
297+
}
298+
Pa_Terminate();
299+
if (mod) {
300+
openmpt_module_destroy(mod);
301+
mod = NULL;
302+
}
303+
304+
// clear canvas on exit
305+
canvas.Clear();
306+
canvas.Send();
307+
308+
if (interrupt_received) return 1;
309+
return 0;
310+
}

0 commit comments

Comments
 (0)