1 /*
2    XMMS-SID - SIDPlay input plugin for X MultiMedia System (XMMS)
3 
4    libSIDPlay v2 support
5 
6    Programmed and designed by Matti 'ccr' Hamalainen <ccr@tnsp.org>
7    (C) Copyright 1999-2009 Tecnic Software productions (TNSP)
8 
9    Ported to sidplayfp:
10    (C) Copyright 2013 Cristian Morales Vega and Hans de Goede
11 
12    This program is free software; you can redistribute it and/or modify
13    it under the terms of the GNU General Public License as published by
14    the Free Software Foundation; either version 2 of the License, or
15    (at your option) any later version.
16 
17    This program is distributed in the hope that it will be useful,
18    but WITHOUT ANY WARRANTY; without even the implied warranty of
19    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20    GNU General Public License for more details.
21 
22    You should have received a copy of the GNU General Public License along
23    with this program; if not, write to the Free Software Foundation, Inc.,
24    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 */
26 
27 #include "xs_config.h"
28 #include "xs_sidplay2.h"
29 
30 #include <pthread.h>
31 #include <string.h>
32 
33 #include <sidplayfp/sidplayfp.h>
34 #include <sidplayfp/SidDatabase.h>
35 #include <sidplayfp/SidInfo.h>
36 #include <sidplayfp/SidTune.h>
37 #include <sidplayfp/SidTuneInfo.h>
38 #include <sidplayfp/builders/residfp.h>
39 
40 #include <libaudcore/runtime.h>
41 #include <libaudcore/vfs.h>
42 
43 struct SidState {
44     sidplayfp *currEng;
45     sidbuilder *currBuilder;
46     SidTune *currTune;
47 
48     SidDatabase database;
49     bool database_loaded = false;
50     pthread_mutex_t database_mutex = PTHREAD_MUTEX_INITIALIZER;
51 };
52 
53 static SidState state;
54 
55 
56 /* Check if we can play the given file
57  */
xs_sidplayfp_probe(const void * buf,int64_t bufSize)58 bool xs_sidplayfp_probe(const void *buf, int64_t bufSize)
59 {
60     if (bufSize < 4)
61         return false;
62 
63     return !memcmp(buf, "PSID", 4) || !memcmp(buf, "RSID", 4);
64 }
65 
66 
67 /* Initialize SIDPlayFP
68  */
xs_sidplayfp_init()69 bool xs_sidplayfp_init()
70 {
71     /* Initialize the engine */
72     state.currEng = new sidplayfp;
73 
74     /* Get current configuration */
75     SidConfig config = state.currEng->config();
76 
77     /* Configure channels and stuff */
78     switch (xs_cfg.audioChannels)
79     {
80     case XS_CHN_STEREO:
81         config.playback = SidConfig::STEREO;
82         break;
83 
84     case XS_CHN_MONO:
85         config.playback = SidConfig::MONO;
86         break;
87     }
88 
89     /* Audio parameters sanity checking and setup */
90     config.frequency = xs_cfg.audioFrequency;
91 
92     /* Initialize builder object */
93     state.currBuilder = new ReSIDfpBuilder("ReSIDfp builder");
94 
95     /* Builder object created, initialize it */
96     state.currBuilder->create(state.currEng->info().maxsids());
97     if (!state.currBuilder->getStatus()) {
98         AUDERR("reSID->create() failed.\n");
99         return false;
100     }
101 
102     state.currBuilder->filter(xs_cfg.emulateFilters);
103     if (!state.currBuilder->getStatus()) {
104         AUDERR("reSID->filter(%d) failed.\n", xs_cfg.emulateFilters);
105         return false;
106     }
107 
108     config.sidEmulation = state.currBuilder;
109 
110     /* Clockspeed settings */
111     switch (xs_cfg.clockSpeed) {
112     case XS_CLOCK_NTSC:
113         config.defaultC64Model = SidConfig::NTSC;
114         break;
115 
116     default:
117         AUDERR("[SIDPlayFP] Invalid clockSpeed=%d, falling back to PAL.\n",
118             xs_cfg.clockSpeed);
119 
120     case XS_CLOCK_PAL:
121         config.defaultC64Model = SidConfig::PAL;
122         xs_cfg.clockSpeed = XS_CLOCK_PAL;
123         break;
124     }
125 
126     config.forceC64Model = xs_cfg.forceSpeed;
127 
128     /* Configure rest of the emulation */
129     if (xs_cfg.mos8580)
130         config.defaultSidModel = SidConfig::MOS8580;
131     else
132         config.defaultSidModel = SidConfig::MOS6581;
133 
134     config.forceSidModel = xs_cfg.forceModel;
135 
136     /* Now set the emulator configuration */
137     if (!state.currEng->config(config)) {
138         AUDERR("[SIDPlayFP] Emulator engine configuration failed!\n");
139         return false;
140     }
141 
142     /* Load ROMs */
143     VFSFile kernal_file("file://" SIDDATADIR "sidplayfp/kernal", "r");
144     VFSFile basic_file("file://" SIDDATADIR "sidplayfp/basic", "r");
145     VFSFile chargen_file("file://" SIDDATADIR "sidplayfp/chargen", "r");
146 
147     if (kernal_file && basic_file && chargen_file)
148     {
149         Index<char> kernal = kernal_file.read_all();
150         Index<char> basic = basic_file.read_all();
151         Index<char> chargen = chargen_file.read_all();
152 
153         if (kernal.len() == 8192 && basic.len() == 8192 && chargen.len() == 4096)
154             state.currEng->setRoms((uint8_t*)kernal.begin(), (uint8_t*)basic.begin(), (uint8_t*)chargen.begin());
155     }
156 
157     /* Load song length database */
158     state.database_loaded = state.database.open(SIDDATADIR "sidplayfp/Songlengths.txt");
159 
160     /* Create the sidtune */
161     state.currTune = new SidTune(0);
162 
163     return true;
164 }
165 
166 
167 /* Close SIDPlayFP engine
168  */
xs_sidplayfp_close()169 void xs_sidplayfp_close()
170 {
171     /* Free internals */
172     if (state.currBuilder) {
173         delete state.currBuilder;
174         state.currBuilder = nullptr;
175     }
176 
177     if (state.currEng) {
178         delete state.currEng;
179         state.currEng = nullptr;
180     }
181 
182     if (state.currTune) {
183         delete state.currTune;
184         state.currTune = nullptr;
185     }
186 
187     if (state.database_loaded)
188         state.database.close();
189 }
190 
191 
192 /* Initialize current song and sub-tune
193  */
xs_sidplayfp_initsong(int subtune)194 bool xs_sidplayfp_initsong(int subtune)
195 {
196     if (!state.currTune->selectSong(subtune)) {
197         AUDERR("[SIDPlayFP] currTune->selectSong() failed\n");
198         return false;
199     }
200 
201     if (!state.currEng->load(state.currTune)) {
202         AUDERR("[SIDPlayFP] currEng->load() failed\n");
203         return false;
204     }
205 
206     return true;
207 }
208 
209 
210 /* Emulate and render audio data to given buffer
211  */
xs_sidplayfp_fillbuffer(char * audioBuffer,unsigned audioBufSize)212 unsigned xs_sidplayfp_fillbuffer(char * audioBuffer, unsigned audioBufSize)
213 {
214     return state.currEng->play((short *)audioBuffer, audioBufSize / 2) * 2;
215 }
216 
217 
218 /* Load a given SID-tune file
219  */
xs_sidplayfp_load(const void * buf,int64_t bufSize)220 bool xs_sidplayfp_load(const void *buf, int64_t bufSize)
221 {
222     /* Try to get the tune */
223     state.currTune->read((const uint8_t*)buf, bufSize);
224 
225     return state.currTune->getStatus();
226 }
227 
228 
xs_sidplayfp_getinfo(xs_tuneinfo_t & ti,const void * buf,int64_t bufSize)229 bool xs_sidplayfp_getinfo(xs_tuneinfo_t &ti, const void *buf, int64_t bufSize)
230 {
231     /* Check if the tune exists and is readable */
232     SidTune myTune((const uint8_t*)buf, bufSize);
233 
234     if (!myTune.getStatus())
235         return false;
236 
237     /* Get general tune information */
238     const SidTuneInfo *myInfo = myTune.getInfo();
239 
240     /* Allocate tuneinfo structure and set information */
241     ti.sidName = String (myInfo->infoString(0));
242     ti.sidComposer = String (myInfo->infoString(1));
243     ti.sidCopyright = String (myInfo->infoString(2));
244 
245     ti.nsubTunes = myInfo->songs();
246     ti.startTune = myInfo->startSong();
247 
248     ti.sidFormat = String (myInfo->formatString());
249 
250     /* Fill in subtune information */
251     ti.subTunes.insert(0, ti.nsubTunes);
252 
253     if (state.database_loaded)
254     {
255         pthread_mutex_lock(&state.database_mutex);
256 
257         for (int i = 0; i < ti.nsubTunes; i++)
258         {
259             myTune.selectSong(i + 1);
260             ti.subTunes[i].tuneLength = state.database.length(myTune);
261         }
262 
263         pthread_mutex_unlock(&state.database_mutex);
264     }
265 
266     return true;
267 }
268