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