qemu/hw/audio/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 "qemu/osdep.h"
  26#include "qapi/error.h"
  27#include "qemu/module.h"
  28#include "hw/hw.h"
  29#include "hw/audio/soundhw.h"
  30#include "audio/audio.h"
  31#include "hw/isa/isa.h"
  32
  33//#define DEBUG
  34
  35#define ADLIB_KILL_TIMERS 1
  36
  37#define ADLIB_DESC "Yamaha YM3812 (OPL2)"
  38
  39#ifdef DEBUG
  40#include "qemu/timer.h"
  41#endif
  42
  43#define dolog(...) AUD_log ("adlib", __VA_ARGS__)
  44#ifdef DEBUG
  45#define ldebug(...) dolog (__VA_ARGS__)
  46#else
  47#define ldebug(...)
  48#endif
  49
  50#include "fmopl.h"
  51#define SHIFT 1
  52
  53#define TYPE_ADLIB "adlib"
  54#define ADLIB(obj) OBJECT_CHECK(AdlibState, (obj), TYPE_ADLIB)
  55
  56typedef struct {
  57    ISADevice parent_obj;
  58
  59    QEMUSoundCard card;
  60    uint32_t freq;
  61    uint32_t port;
  62    int ticking[2];
  63    int enabled;
  64    int active;
  65    int bufpos;
  66#ifdef DEBUG
  67    int64_t exp[2];
  68#endif
  69    int16_t *mixbuf;
  70    uint64_t dexp[2];
  71    SWVoiceOut *voice;
  72    int left, pos, samples;
  73    QEMUAudioTimeStamp ats;
  74    FM_OPL *opl;
  75    PortioList port_list;
  76} AdlibState;
  77
  78static void adlib_stop_opl_timer (AdlibState *s, size_t n)
  79{
  80    OPLTimerOver (s->opl, n);
  81    s->ticking[n] = 0;
  82}
  83
  84static void adlib_kill_timers (AdlibState *s)
  85{
  86    size_t i;
  87
  88    for (i = 0; i < 2; ++i) {
  89        if (s->ticking[i]) {
  90            uint64_t delta;
  91
  92            delta = AUD_get_elapsed_usec_out (s->voice, &s->ats);
  93            ldebug (
  94                "delta = %f dexp = %f expired => %d\n",
  95                delta / 1000000.0,
  96                s->dexp[i] / 1000000.0,
  97                delta >= s->dexp[i]
  98                );
  99            if (ADLIB_KILL_TIMERS || delta >= s->dexp[i]) {
 100                adlib_stop_opl_timer (s, i);
 101                AUD_init_time_stamp_out (s->voice, &s->ats);
 102            }
 103        }
 104    }
 105}
 106
 107static void adlib_write(void *opaque, uint32_t nport, uint32_t val)
 108{
 109    AdlibState *s = opaque;
 110    int a = nport & 3;
 111
 112    s->active = 1;
 113    AUD_set_active_out (s->voice, 1);
 114
 115    adlib_kill_timers (s);
 116
 117    OPLWrite (s->opl, a, val);
 118}
 119
 120static uint32_t adlib_read(void *opaque, uint32_t nport)
 121{
 122    AdlibState *s = opaque;
 123    uint8_t data;
 124    int a = nport & 3;
 125
 126    adlib_kill_timers (s);
 127    data = OPLRead (s->opl, a);
 128
 129    return data;
 130}
 131
 132static void timer_handler (void *opaque, int c, double interval_Sec)
 133{
 134    AdlibState *s = opaque;
 135    unsigned n = c & 1;
 136#ifdef DEBUG
 137    double interval;
 138    int64_t exp;
 139#endif
 140
 141    if (interval_Sec == 0.0) {
 142        s->ticking[n] = 0;
 143        return;
 144    }
 145
 146    s->ticking[n] = 1;
 147#ifdef DEBUG
 148    interval = NANOSECONDS_PER_SECOND * interval_Sec;
 149    exp = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + interval;
 150    s->exp[n] = exp;
 151#endif
 152
 153    s->dexp[n] = interval_Sec * 1000000.0;
 154    AUD_init_time_stamp_out (s->voice, &s->ats);
 155}
 156
 157static int write_audio (AdlibState *s, int samples)
 158{
 159    int net = 0;
 160    int pos = s->pos;
 161
 162    while (samples) {
 163        int nbytes, wbytes, wsampl;
 164
 165        nbytes = samples << SHIFT;
 166        wbytes = AUD_write (
 167            s->voice,
 168            s->mixbuf + (pos << (SHIFT - 1)),
 169            nbytes
 170            );
 171
 172        if (wbytes) {
 173            wsampl = wbytes >> SHIFT;
 174
 175            samples -= wsampl;
 176            pos = (pos + wsampl) % s->samples;
 177
 178            net += wsampl;
 179        }
 180        else {
 181            break;
 182        }
 183    }
 184
 185    return net;
 186}
 187
 188static void adlib_callback (void *opaque, int free)
 189{
 190    AdlibState *s = opaque;
 191    int samples, net = 0, to_play, written;
 192
 193    samples = free >> SHIFT;
 194    if (!(s->active && s->enabled) || !samples) {
 195        return;
 196    }
 197
 198    to_play = audio_MIN (s->left, samples);
 199    while (to_play) {
 200        written = write_audio (s, to_play);
 201
 202        if (written) {
 203            s->left -= written;
 204            samples -= written;
 205            to_play -= written;
 206            s->pos = (s->pos + written) % s->samples;
 207        }
 208        else {
 209            return;
 210        }
 211    }
 212
 213    samples = audio_MIN (samples, s->samples - s->pos);
 214    if (!samples) {
 215        return;
 216    }
 217
 218    YM3812UpdateOne (s->opl, s->mixbuf + s->pos, samples);
 219
 220    while (samples) {
 221        written = write_audio (s, samples);
 222
 223        if (written) {
 224            net += written;
 225            samples -= written;
 226            s->pos = (s->pos + written) % s->samples;
 227        }
 228        else {
 229            s->left = samples;
 230            return;
 231        }
 232    }
 233}
 234
 235static void Adlib_fini (AdlibState *s)
 236{
 237    if (s->opl) {
 238        OPLDestroy (s->opl);
 239        s->opl = NULL;
 240    }
 241
 242    g_free(s->mixbuf);
 243
 244    s->active = 0;
 245    s->enabled = 0;
 246    AUD_remove_card (&s->card);
 247}
 248
 249static MemoryRegionPortio adlib_portio_list[] = {
 250    { 0, 4, 1, .read = adlib_read, .write = adlib_write, },
 251    { 0, 2, 1, .read = adlib_read, .write = adlib_write, },
 252    { 0x388, 4, 1, .read = adlib_read, .write = adlib_write, },
 253    PORTIO_END_OF_LIST(),
 254};
 255
 256static void adlib_realizefn (DeviceState *dev, Error **errp)
 257{
 258    AdlibState *s = ADLIB(dev);
 259    struct audsettings as;
 260
 261    s->opl = OPLCreate (3579545, s->freq);
 262    if (!s->opl) {
 263        error_setg (errp, "OPLCreate %d failed", s->freq);
 264        return;
 265    }
 266    else {
 267        OPLSetTimerHandler(s->opl, timer_handler, s);
 268        s->enabled = 1;
 269    }
 270
 271    as.freq = s->freq;
 272    as.nchannels = SHIFT;
 273    as.fmt = AUDIO_FORMAT_S16;
 274    as.endianness = AUDIO_HOST_ENDIANNESS;
 275
 276    AUD_register_card ("adlib", &s->card);
 277
 278    s->voice = AUD_open_out (
 279        &s->card,
 280        s->voice,
 281        "adlib",
 282        s,
 283        adlib_callback,
 284        &as
 285        );
 286    if (!s->voice) {
 287        Adlib_fini (s);
 288        error_setg (errp, "Initializing audio voice failed");
 289        return;
 290    }
 291
 292    s->samples = AUD_get_buffer_size_out (s->voice) >> SHIFT;
 293    s->mixbuf = g_malloc0 (s->samples << SHIFT);
 294
 295    adlib_portio_list[0].offset = s->port;
 296    adlib_portio_list[1].offset = s->port + 8;
 297    portio_list_init (&s->port_list, OBJECT(s), adlib_portio_list, s, "adlib");
 298    portio_list_add (&s->port_list, isa_address_space_io(&s->parent_obj), 0);
 299}
 300
 301static Property adlib_properties[] = {
 302    DEFINE_PROP_UINT32 ("iobase",  AdlibState, port, 0x220),
 303    DEFINE_PROP_UINT32 ("freq",    AdlibState, freq,  44100),
 304    DEFINE_PROP_END_OF_LIST (),
 305};
 306
 307static void adlib_class_initfn (ObjectClass *klass, void *data)
 308{
 309    DeviceClass *dc = DEVICE_CLASS (klass);
 310
 311    dc->realize = adlib_realizefn;
 312    set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
 313    dc->desc = ADLIB_DESC;
 314    dc->props = adlib_properties;
 315}
 316
 317static const TypeInfo adlib_info = {
 318    .name          = TYPE_ADLIB,
 319    .parent        = TYPE_ISA_DEVICE,
 320    .instance_size = sizeof (AdlibState),
 321    .class_init    = adlib_class_initfn,
 322};
 323
 324static int Adlib_init (ISABus *bus)
 325{
 326    isa_create_simple (bus, TYPE_ADLIB);
 327    return 0;
 328}
 329
 330static void adlib_register_types (void)
 331{
 332    type_register_static (&adlib_info);
 333    isa_register_soundhw("adlib", ADLIB_DESC, Adlib_init);
 334}
 335
 336type_init (adlib_register_types)
 337