qemu/hw/adlib.c
<<
>>
Prefs
   1/*
   2 * QEMU Proxy for OPL2/3 emulation by MAME team
   3 *
   4 * Copyright (c) 2004-2005 Vassili Karpov (malc)
   5 *
   6 * Permission is hereby granted, free of charge, to any person obtaining a copy
   7 * of this software and associated documentation files (the "Software"), to deal
   8 * in the Software without restriction, including without limitation the rights
   9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10 * copies of the Software, and to permit persons to whom the Software is
  11 * furnished to do so, subject to the following conditions:
  12 *
  13 * The above copyright notice and this permission notice shall be included in
  14 * all copies or substantial portions of the Software.
  15 *
  16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22 * THE SOFTWARE.
  23 */
  24
  25#include "hw.h"
  26#include "audiodev.h"
  27#include "audio/audio.h"
  28#include "isa.h"
  29
  30//#define DEBUG
  31
  32#define ADLIB_KILL_TIMERS 1
  33
  34#ifdef DEBUG
  35#include "qemu-timer.h"
  36#endif
  37
  38#define dolog(...) AUD_log ("adlib", __VA_ARGS__)
  39#ifdef DEBUG
  40#define ldebug(...) dolog (__VA_ARGS__)
  41#else
  42#define ldebug(...)
  43#endif
  44
  45#ifdef HAS_YMF262
  46#include "ymf262.h"
  47void YMF262UpdateOneQEMU (int which, INT16 *dst, int length);
  48#define SHIFT 2
  49#else
  50#include "fmopl.h"
  51#define SHIFT 1
  52#endif
  53
  54#define IO_READ_PROTO(name) \
  55    uint32_t name (void *opaque, uint32_t nport)
  56#define IO_WRITE_PROTO(name) \
  57    void name (void *opaque, uint32_t nport, uint32_t val)
  58
  59static struct {
  60    int port;
  61    int freq;
  62} conf = {0x220, 44100};
  63
  64typedef struct {
  65    QEMUSoundCard card;
  66    int ticking[2];
  67    int enabled;
  68    int active;
  69    int bufpos;
  70#ifdef DEBUG
  71    int64_t exp[2];
  72#endif
  73    int16_t *mixbuf;
  74    uint64_t dexp[2];
  75    SWVoiceOut *voice;
  76    int left, pos, samples;
  77    QEMUAudioTimeStamp ats;
  78#ifndef HAS_YMF262
  79    FM_OPL *opl;
  80#endif
  81} AdlibState;
  82
  83static AdlibState glob_adlib;
  84
  85static void adlib_stop_opl_timer (AdlibState *s, size_t n)
  86{
  87#ifdef HAS_YMF262
  88    YMF262TimerOver (0, n);
  89#else
  90    OPLTimerOver (s->opl, n);
  91#endif
  92    s->ticking[n] = 0;
  93}
  94
  95static void adlib_kill_timers (AdlibState *s)
  96{
  97    size_t i;
  98
  99    for (i = 0; i < 2; ++i) {
 100        if (s->ticking[i]) {
 101            uint64_t delta;
 102
 103            delta = AUD_get_elapsed_usec_out (s->voice, &s->ats);
 104            ldebug (
 105                "delta = %f dexp = %f expired => %d\n",
 106                delta / 1000000.0,
 107                s->dexp[i] / 1000000.0,
 108                delta >= s->dexp[i]
 109                );
 110            if (ADLIB_KILL_TIMERS || delta >= s->dexp[i]) {
 111                adlib_stop_opl_timer (s, i);
 112                AUD_init_time_stamp_out (s->voice, &s->ats);
 113            }
 114        }
 115    }
 116}
 117
 118static IO_WRITE_PROTO (adlib_write)
 119{
 120    AdlibState *s = opaque;
 121    int a = nport & 3;
 122    int status;
 123
 124    s->active = 1;
 125    AUD_set_active_out (s->voice, 1);
 126
 127    adlib_kill_timers (s);
 128
 129#ifdef HAS_YMF262
 130    status = YMF262Write (0, a, val);
 131#else
 132    status = OPLWrite (s->opl, a, val);
 133#endif
 134}
 135
 136static IO_READ_PROTO (adlib_read)
 137{
 138    AdlibState *s = opaque;
 139    uint8_t data;
 140    int a = nport & 3;
 141
 142    adlib_kill_timers (s);
 143
 144#ifdef HAS_YMF262
 145    data = YMF262Read (0, a);
 146#else
 147    data = OPLRead (s->opl, a);
 148#endif
 149    return data;
 150}
 151
 152static void timer_handler (int c, double interval_Sec)
 153{
 154    AdlibState *s = &glob_adlib;
 155    unsigned n = c & 1;
 156#ifdef DEBUG
 157    double interval;
 158    int64_t exp;
 159#endif
 160
 161    if (interval_Sec == 0.0) {
 162        s->ticking[n] = 0;
 163        return;
 164    }
 165
 166    s->ticking[n] = 1;
 167#ifdef DEBUG
 168    interval = ticks_per_sec * interval_Sec;
 169    exp = qemu_get_clock (vm_clock) + interval;
 170    s->exp[n] = exp;
 171#endif
 172
 173    s->dexp[n] = interval_Sec * 1000000.0;
 174    AUD_init_time_stamp_out (s->voice, &s->ats);
 175}
 176
 177static int write_audio (AdlibState *s, int samples)
 178{
 179    int net = 0;
 180    int pos = s->pos;
 181
 182    while (samples) {
 183        int nbytes, wbytes, wsampl;
 184
 185        nbytes = samples << SHIFT;
 186        wbytes = AUD_write (
 187            s->voice,
 188            s->mixbuf + (pos << (SHIFT - 1)),
 189            nbytes
 190            );
 191
 192        if (wbytes) {
 193            wsampl = wbytes >> SHIFT;
 194
 195            samples -= wsampl;
 196            pos = (pos + wsampl) % s->samples;
 197
 198            net += wsampl;
 199        }
 200        else {
 201            break;
 202        }
 203    }
 204
 205    return net;
 206}
 207
 208static void adlib_callback (void *opaque, int free)
 209{
 210    AdlibState *s = opaque;
 211    int samples, net = 0, to_play, written;
 212
 213    samples = free >> SHIFT;
 214    if (!(s->active && s->enabled) || !samples) {
 215        return;
 216    }
 217
 218    to_play = audio_MIN (s->left, samples);
 219    while (to_play) {
 220        written = write_audio (s, to_play);
 221
 222        if (written) {
 223            s->left -= written;
 224            samples -= written;
 225            to_play -= written;
 226            s->pos = (s->pos + written) % s->samples;
 227        }
 228        else {
 229            return;
 230        }
 231    }
 232
 233    samples = audio_MIN (samples, s->samples - s->pos);
 234    if (!samples) {
 235        return;
 236    }
 237
 238#ifdef HAS_YMF262
 239    YMF262UpdateOneQEMU (0, s->mixbuf + s->pos * 2, samples);
 240#else
 241    YM3812UpdateOne (s->opl, s->mixbuf + s->pos, samples);
 242#endif
 243
 244    while (samples) {
 245        written = write_audio (s, samples);
 246
 247        if (written) {
 248            net += written;
 249            samples -= written;
 250            s->pos = (s->pos + written) % s->samples;
 251        }
 252        else {
 253            s->left = samples;
 254            return;
 255        }
 256    }
 257}
 258
 259static void Adlib_fini (AdlibState *s)
 260{
 261#ifdef HAS_YMF262
 262    YMF262Shutdown ();
 263#else
 264    if (s->opl) {
 265        OPLDestroy (s->opl);
 266        s->opl = NULL;
 267    }
 268#endif
 269
 270    if (s->mixbuf) {
 271        qemu_free (s->mixbuf);
 272    }
 273
 274    s->active = 0;
 275    s->enabled = 0;
 276    AUD_remove_card (&s->card);
 277}
 278
 279int Adlib_init (qemu_irq *pic)
 280{
 281    AdlibState *s = &glob_adlib;
 282    struct audsettings as;
 283
 284#ifdef HAS_YMF262
 285    if (YMF262Init (1, 14318180, conf.freq)) {
 286        dolog ("YMF262Init %d failed\n", conf.freq);
 287        return -1;
 288    }
 289    else {
 290        YMF262SetTimerHandler (0, timer_handler, 0);
 291        s->enabled = 1;
 292    }
 293#else
 294    s->opl = OPLCreate (OPL_TYPE_YM3812, 3579545, conf.freq);
 295    if (!s->opl) {
 296        dolog ("OPLCreate %d failed\n", conf.freq);
 297        return -1;
 298    }
 299    else {
 300        OPLSetTimerHandler (s->opl, timer_handler, 0);
 301        s->enabled = 1;
 302    }
 303#endif
 304
 305    as.freq = conf.freq;
 306    as.nchannels = SHIFT;
 307    as.fmt = AUD_FMT_S16;
 308    as.endianness = AUDIO_HOST_ENDIANNESS;
 309
 310    AUD_register_card ("adlib", &s->card);
 311
 312    s->voice = AUD_open_out (
 313        &s->card,
 314        s->voice,
 315        "adlib",
 316        s,
 317        adlib_callback,
 318        &as
 319        );
 320    if (!s->voice) {
 321        Adlib_fini (s);
 322        return -1;
 323    }
 324
 325    s->samples = AUD_get_buffer_size_out (s->voice) >> SHIFT;
 326    s->mixbuf = qemu_mallocz (s->samples << SHIFT);
 327
 328    register_ioport_read (0x388, 4, 1, adlib_read, s);
 329    register_ioport_write (0x388, 4, 1, adlib_write, s);
 330
 331    register_ioport_read (conf.port, 4, 1, adlib_read, s);
 332    register_ioport_write (conf.port, 4, 1, adlib_write, s);
 333
 334    register_ioport_read (conf.port + 8, 2, 1, adlib_read, s);
 335    register_ioport_write (conf.port + 8, 2, 1, adlib_write, s);
 336
 337    return 0;
 338}
 339