remove/branch audio; remove debugging initial disk insertion; fix order of params in blit

This commit is contained in:
Jorj Bauer 2017-08-27 07:51:21 -04:00
parent dd523aac98
commit 89a6450a91
24 changed files with 3 additions and 27816 deletions

View File

@ -6,7 +6,7 @@ CXXFLAGS=-Wall -I .. -I . -I apple -I sdl -I/usr/local/include/SDL2 -O3 -g
TSRC=cpu.cpp util/testharness.cpp
COMMONOBJS=cpu.o apple/appledisplay.o apple/applekeyboard.o apple/applemmu.o apple/applevm.o apple/diskii.o apple/nibutil.o RingBuffer.o globals.o apple/parallelcard.o apple/fx80.o apple/mockingboard.o apple/sy6522.o apple/ay8910.o lcg.o
COMMONOBJS=cpu.o apple/appledisplay.o apple/applekeyboard.o apple/applemmu.o apple/applevm.o apple/diskii.o apple/nibutil.o RingBuffer.o globals.o apple/parallelcard.o apple/fx80.o apple/sy6522.o apple/ay8910.o lcg.o
SDLOBJS=sdl/sdl-speaker.o sdl/sdl-display.o sdl/sdl-keyboard.o sdl/sdl-paddles.o sdl/sdl-filemanager.o sdl/aiie.o sdl/sdl-printer.o sdl/sdl-clock.o

View File

@ -10,7 +10,6 @@
#include "applemmu-rom.h"
#include "physicalspeaker.h"
#include "cpu.h"
#include "mockingboard.h"
#include "globals.h"
@ -92,11 +91,6 @@ uint8_t AppleMMU::read(uint16_t address)
updateMemoryPages();
}
// FIXME: assumes slot 4 is a mockingboard
if (slots[4] && address >= 0xC400 && address <= 0xC4FF) {
return ((Mockingboard *)slots[4])->read(address);
}
uint8_t res = readPages[address >> 8][address & 0xFF];
return res;
@ -115,12 +109,6 @@ void AppleMMU::write(uint16_t address, uint8_t v)
return writeSwitches(address, v);
}
// FIXME: assumes slot4 is a mockingboard
if (slots[4] && address >= 0xC400 && address <= 0xC4FF) {
((Mockingboard *)slots[4])->write(address, v);
return;
}
// Don't allow writes to ROM
// Hard ROM, I/O, slots, whatnot
if (address >= 0xC100 && address <= 0xCFFF)

View File

@ -23,11 +23,6 @@ AppleVM::AppleVM()
parallel = new ParallelCard();
((AppleMMU *)mmu)->setSlot(1, parallel);
mockingboard = NULL;
/*
mockingboard = new Mockingboard();
((AppleMMU *)mmu)->setSlot(4, mockingboard);*/
#ifdef TEENSYDUINO
teensyClock = new TeensyClock((AppleMMU *)mmu);
((AppleMMU *)mmu)->setSlot(7, teensyClock);
@ -46,8 +41,6 @@ AppleVM::~AppleVM()
#endif
delete disk6;
delete parallel;
if (mockingboard)
delete mockingboard;
}
// fixme: make member vars
@ -68,8 +61,6 @@ void AppleVM::cpuMaintenance(uint32_t cycles)
}
keyboard->maintainKeyboard(cycles);
if (mockingboard)
mockingboard->update(cycles);
}
void AppleVM::Reset()

View File

@ -6,7 +6,6 @@
#include "diskii.h"
#include "vmkeyboard.h"
#include "parallelcard.h"
#include "mockingboard.h"
#ifdef TEENSYDUINO
#include "teensy-clock.h"
#else
@ -36,7 +35,6 @@ class AppleVM : public VM {
protected:
VMKeyboard *keyboard;
ParallelCard *parallel;
Mockingboard *mockingboard;
#ifdef TEENSYDUINO
TeensyClock *teensyClock;
#else

View File

@ -1,64 +0,0 @@
#include "mockingboard.h"
#include <string.h>
Mockingboard::Mockingboard()
{
}
Mockingboard::~Mockingboard()
{
}
void Mockingboard::Reset()
{
}
uint8_t Mockingboard::readSwitches(uint8_t s)
{
// There are never any reads to the I/O switches
return 0xFF;
}
void Mockingboard::writeSwitches(uint8_t s, uint8_t v)
{
// There are never any writes to the I/O switches
}
void Mockingboard::loadROM(uint8_t *toWhere)
{
// We don't need a ROM; we're going to work via direct interaction
// with memory 0xC400 - 0xC4FF
}
uint8_t Mockingboard::read(uint16_t address)
{
address &= 0xFF;
if ( (address >= 0x00 &&
address <= 0x0F) ||
(address >= 0x80 &&
address <= 0x8F) ) {
uint8_t idx = (address & 0x80 ? 1 : 0);
return sy6522[idx].read(address & 0x0F);
}
return 0xFF;
}
void Mockingboard::write(uint16_t address, uint8_t val)
{
address &= 0xFF;
if ( (address >= 0x00 &&
address <= 0x0F) ||
(address >= 0x80 &&
address <= 0x8F) ) {
uint8_t idx = (address & 0x80 ? 1 : 0);
return sy6522[idx].write(address & 0x0F, val);
}
}
void Mockingboard::update(uint32_t cycles)
{
sy6522[0].update(cycles);
sy6522[1].update(cycles);
}

View File

@ -1,34 +0,0 @@
#ifndef __MOCKINGBOARD_H
#define __MOCKINGBOARD_H
#ifdef TEENSYDUINO
#include <Arduino.h>
#else
#include <stdint.h>
#include <stdio.h>
#endif
#include "applemmu.h"
#include "Slot.h"
#include "sy6522.h"
class Mockingboard : public Slot {
public:
Mockingboard();
virtual ~Mockingboard();
virtual void Reset(); // used by BIOS cold-boot
virtual uint8_t readSwitches(uint8_t s);
virtual void writeSwitches(uint8_t s, uint8_t v);
virtual void loadROM(uint8_t *toWhere);
void update(uint32_t cycles);
uint8_t read(uint16_t address);
void write(uint16_t address, uint8_t val);
private:
SY6522 sy6522[2];
};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,129 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
#ifndef AY8910_H
#define AY8910_H
#define MAX_8910 4
//-------------------------------------
// MAME interface
void _AYWriteReg(int chip, int r, int v
#if MB_TRACING
, FILE *mb_trace_fp
#endif
);
//void AY8910_write_ym(int chip, int addr, int data);
void AY8910_reset(int chip);
void AY8910Update(int chip, int16_t** buffer, int nNumSamples);
void AY8910_InitAll(int nClock, unsigned long nSampleRate);
void AY8910_InitClock(int nClock, unsigned long nSampleRate);
uint8_t* AY8910_GetRegsPtr(unsigned int uChip);
void AY8910UpdateSetCycles();
#if 1 // APPLE2IX
bool _ay8910_saveState(StateHelper_s *helper, unsigned int chip);
bool _ay8910_loadState(StateHelper_s *helper, unsigned int chip);
# if TESTING
int _ay8910_testAssertA2V2(unsigned int chip, uint8_t **exData);
# endif
#else
UINT AY8910_SaveSnapshot(class YamlSaveHelper& yamlSaveHelper, UINT uChip, std::string& suffix);
UINT AY8910_LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT uChip, std::string& suffix);
#endif
#if MB_TRACING
void _mb_trace_AY8910(int chip, FILE *mb_trace_fp);
#endif
//-------------------------------------
// FUSE stuff
typedef unsigned long libspectrum_dword;
typedef uint8_t libspectrum_byte;
typedef int16_t libspectrum_signed_word;
struct CAY8910;
/* max. number of sub-frame AY port writes allowed;
* given the number of port writes theoretically possible in a
* 50th I think this should be plenty.
*/
#define AY_CHANGE_MAX 8000
/*
class CAY8910
{
public:
*/
void CAY8910_init(struct CAY8910 *_this);
void sound_ay_init(struct CAY8910 *_this);
//void sound_init(struct CAY8910 *_this, const char *device);
void sound_ay_write(struct CAY8910 *_this, int reg, int val, libspectrum_dword now);
void sound_ay_reset(struct CAY8910 *_this);
void sound_frame(struct CAY8910 *_this);
uint8_t* GetAYRegsPtr(struct CAY8910 *_this);
void SetCLK(double CLK);
#if 1 // APPLE2IX
#else
void SaveSnapshot(class YamlSaveHelper& yamlSaveHelper, std::string& suffix);
bool LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, std::string& suffix);
#endif
/*
private:
void init( void );
void sound_end( void );
void sound_ay_overlay( void );
*/
typedef struct ay_change_tag
{
libspectrum_dword tstates;
unsigned short ofs;
unsigned char reg, val;
} ay_change_tag;
/*
private:
*/
typedef struct CAY8910
{
/* foo_subcycles are fixed-point with low 16 bits as fractional part.
* The other bits count as the chip does.
*/
unsigned int ay_tone_tick[3], ay_tone_high[3], ay_noise_tick;
unsigned int ay_tone_subcycles, ay_env_subcycles;
unsigned int ay_env_internal_tick, ay_env_tick;
unsigned int ay_tick_incr;
unsigned int ay_tone_period[3], ay_noise_period, ay_env_period;
//static int beeper_last_subpos[2] = { 0, 0 };
/* Local copy of the AY registers */
libspectrum_byte sound_ay_registers[16];
struct ay_change_tag ay_change[ AY_CHANGE_MAX ];
int ay_change_count;
// statics from sound_ay_overlay()
int rng;
int noise_toggle;
int env_first, env_rev, env_counter;
} CAY8910;
// Vars shared between all AY's
extern double m_fCurrentCLK_AY8910;
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,84 +0,0 @@
/*
* OpenAL Helpers
*
* Copyright (c) 2011 by Chris Robinson <chris.kcat@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/* This file contains routines to help with some menial OpenAL-related tasks,
* such as opening a device and setting up a context, closing the device and
* destroying its context, converting between frame counts and byte lengths,
* finding an appropriate buffer format, and getting readable strings for
* channel configs and sample types. */
#include "common.h"
#include "audio/alhelpers.h"
/* InitAL opens the default device and sets up a context using default
* attributes, making the program ready to call OpenAL functions. */
ALCcontext* InitAL(void)
{
ALCdevice *device;
ALCcontext *ctx;
/* Open and initialize a device with default settings */
device = alcOpenDevice(NULL);
if(!device)
{
ERRLOG("Could not open a device!");
return NULL;
}
ctx = alcCreateContext(device, NULL);
if(ctx == NULL || alcMakeContextCurrent(ctx) == ALC_FALSE)
{
if(ctx != NULL)
{
alcDestroyContext(ctx);
}
alcCloseDevice(device);
ERRLOG("Could not set a context!");
return NULL;
}
LOG("Opened \"%s\"", alcGetString(device, ALC_DEVICE_SPECIFIER));
return ctx;
}
/* CloseAL closes the device belonging to the current context, and destroys the
* context. */
void CloseAL(void)
{
ALCdevice *device;
ALCcontext *ctx;
ctx = alcGetCurrentContext();
if(ctx == NULL)
{
return;
}
device = alcGetContextsDevice(ctx);
alcMakeContextCurrent(NULL);
alcDestroyContext(ctx);
alcCloseDevice(device);
}

View File

@ -1,32 +0,0 @@
#ifndef ALHELPERS_H
#define ALHELPERS_H
#ifdef __APPLE__
# import <OpenAL/al.h>
# import <OpenAL/alc.h>
#else
# include <AL/al.h>
# include <AL/alc.h>
# include <AL/alext.h>
#endif
#ifndef _WIN32
#include <unistd.h>
#else
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/* Easy device init/deinit functions. */
ALCcontext* InitAL(void);
void CloseAL(void);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* ALHELPERS_H */

File diff suppressed because it is too large Load Diff

View File

@ -1,146 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
#ifndef _MOCKINGBOARD_H_
#define _MOCKINGBOARD_H_
#ifdef APPLE2IX
#include "audio/peripherals.h"
extern bool g_bDisableDirectSoundMockingboard;
typedef struct
{
union
{
struct
{
uint8_t l;
uint8_t h;
};
uint16_t w;
};
} IWORD;
typedef struct
{
uint8_t ORB; // $00 - Port B
uint8_t ORA; // $01 - Port A (with handshaking)
uint8_t DDRB; // $02 - Data Direction Register B
uint8_t DDRA; // $03 - Data Direction Register A
//
// $04 - Read counter (L) / Write latch (L)
// $05 - Read / Write & initiate count (H)
// $06 - Read / Write & latch (L)
// $07 - Read / Write & latch (H)
// $08 - Read counter (L) / Write latch (L)
// $09 - Read counter (H) / Write latch (H)
IWORD TIMER1_COUNTER;
IWORD TIMER1_LATCH;
IWORD TIMER2_COUNTER;
IWORD TIMER2_LATCH;
//
uint8_t SERIAL_SHIFT; // $0A
uint8_t ACR; // $0B - Auxiliary Control Register
uint8_t PCR; // $0C - Peripheral Control Register
uint8_t IFR; // $0D - Interrupt Flag Register
uint8_t IER; // $0E - Interrupt Enable Register
uint8_t ORA_NO_HS; // $0F - Port A (without handshaking)
} SY6522;
typedef struct
{
uint8_t DurationPhoneme;
uint8_t Inflection; // I10..I3
uint8_t RateInflection;
uint8_t CtrlArtAmp;
uint8_t FilterFreq;
//
uint8_t CurrentMode; // b7:6=Mode; b0=D7 pin (for IRQ)
} SSI263A;
extern SS_CARDTYPE g_Slot4; // Mockingboard, Z80, Mouse in slot4
extern SS_CARDTYPE g_Slot5; // Mockingboard, Z80 in slot5
#define MB_UNITS_PER_CARD 2
typedef struct
{
SY6522 RegsSY6522;
uint8_t RegsAY8910[16];
SSI263A RegsSSI263;
uint8_t nAYCurrentRegister;
bool bTimer1IrqPending;
bool bTimer2IrqPending;
bool bSpeechIrqPending;
} MB_Unit;
typedef struct
{
SS_CARD_HDR Hdr;
MB_Unit Unit[MB_UNITS_PER_CARD];
} SS_CARD_MOCKINGBOARD;
#endif
extern bool g_bMBTimerIrqActive;
#ifdef _DEBUG
extern uint32_t g_uTimer1IrqCount; // DEBUG
#endif
void MB_Initialize();
void MB_Reinitialize();
void MB_Destroy();
void MB_SetEnabled(bool enabled);
bool MB_ISEnabled(void);
void MB_Reset();
void MB_InitializeIO(char *pCxRomPeripheral, unsigned int uSlot4, unsigned int uSlot5);
void MB_Mute();
void MB_Demute();
void MB_StartOfCpuExecute();
void MB_EndOfVideoFrame();
void MB_UpdateCycles(void);
SS_CARDTYPE MB_GetSoundcardType();
void MB_SetSoundcardType(SS_CARDTYPE NewSoundcardType);
double MB_GetFramePeriod();
bool MB_IsActive();
unsigned long MB_GetVolume();
void MB_SetVolumeZeroToTen(unsigned long goesToTen);
void MB_SetVolume(unsigned long dwVolume, unsigned long dwVolumeMax);
#if 1 // APPLE2IX
bool mb_saveState(StateHelper_s *helper);
bool mb_loadState(StateHelper_s *helper);
# if TESTING
int mb_testAssertA2V2(uint8_t *exData, size_t dataSiz);
# endif
#else
void MB_GetSnapshot_v1(struct SS_CARD_MOCKINGBOARD_v1* const pSS, const DWORD dwSlot); // For debugger
int MB_SetSnapshot_v1(const struct SS_CARD_MOCKINGBOARD_v1* const pSS, const DWORD dwSlot);
std::string MB_GetSnapshotCardName(void);
void MB_SaveSnapshot(class YamlSaveHelper& yamlSaveHelper, const UINT uSlot);
bool MB_LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version);
#endif
#if 1 // APPLE2IX
uint8_t mb_read(uint16_t ea);
void mb_io_initialize(unsigned int slot4, unsigned int slot5);
# if MB_TRACING
void mb_traceBegin(const char *trace_file);
void mb_traceFlush(void);
void mb_traceEnd(void);
# endif
#endif
#if UNBREAK_SOON
std::string Phasor_GetSnapshotCardName(void);
void Phasor_SaveSnapshot(class YamlSaveHelper& yamlSaveHelper, const UINT uSlot);
bool Phasor_LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version);
#endif
#endif // whole file

View File

@ -1,56 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
#ifndef _PERIPHERALS_H_
#define _PERIPHERALS_H_
#include "common.h"
typedef enum eIRQSRC {
IS_6522=0x08, // NOTE : matches IRQ... defines in cpu.h
IS_SPEECH=0x10,
IS_SSC=0x20,
IS_MOUSE=0x40
} eIRQSRC;
typedef enum SS_CARDTYPE
{
CT_Empty = 0,
CT_Disk2, // Apple Disk][
CT_SSC, // Apple Super Serial Card
CT_MockingboardC, // Soundcard
CT_GenericPrinter,
CT_GenericHDD, // Hard disk
CT_GenericClock,
CT_MouseInterface,
CT_Z80,
CT_Phasor, // Soundcard
CT_Echo, // Soundcard
CT_SAM, // Soundcard: Software Automated Mouth
} SS_CARDTYPE;
typedef struct
{
unsigned long dwLength; // Byte length of this unit struct
unsigned long dwVersion;
} SS_UNIT_HDR;
typedef struct
{
SS_UNIT_HDR UnitHdr;
unsigned long dwType; // SS_CARDTYPE
unsigned long dwSlot; // [1..7]
} SS_CARD_HDR;
void CpuIrqAssert(eIRQSRC irq_source);
void CpuIrqDeassert(eIRQSRC irq_source);
#endif

View File

@ -1,804 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
#include "common.h"
#include "playqueue.h"
typedef struct PQListNode_s {
struct PQListNode_s *next;
struct PQListNode_s *prev;
PlayNode_s node;
} PQListNode_s;
typedef struct PQList_s {
PQListNode_s *availNodes; // ->next only LL
PQListNode_s *queuedNodes; // double LL
PQListNode_s *queuedHead;
} PQList_s;
// ----------------------------------------------------------------------------
static inline bool _iterate_list_and_find_node(INOUT PQListNode_s **listNodePtr, long nodeId) {
while (*listNodePtr) {
if ((*listNodePtr)->node.nodeId == nodeId) {
return true;
}
*listNodePtr = (*listNodePtr)->next;
}
return false;
}
static long playq_enqueue(PlayQueue_s *_this, INOUT PlayNode_s *node) {
long err = 0;
do {
PQList_s *list = (PQList_s *)(_this->_internal);
// detach a node from the available pool
PQListNode_s *listNode = list->availNodes;
if (!listNode) {
ERRLOG("Cannot enqueue: no slots available");
err = -1;
break;
}
list->availNodes = listNode->next;
// exchange data
listNode->node.numBytes = node->numBytes;
listNode->node.bytes = node->bytes;
node->nodeId = listNode->node.nodeId;
// enqueue node
listNode->next = list->queuedNodes;
listNode->prev = NULL;
if (list->queuedNodes) {
list->queuedNodes->prev = listNode;
}
list->queuedNodes = listNode;
// reset head
if (listNode->next == NULL) {
list->queuedHead = listNode;
}
} while (0);
return err;
}
static long playq_dequeue(PlayQueue_s *_this, OUTPARM PlayNode_s *node) {
PQList_s *list = (PQList_s *)(_this->_internal);
if (node) {
*node = (PlayNode_s){ 0 };
}
bool found = (list->queuedHead != NULL);
if (found) {
PQListNode_s *listNode = list->queuedHead;
// detach from queued
list->queuedHead = list->queuedHead->prev;
if (list->queuedHead) {
list->queuedHead->next = NULL;
} else {
list->queuedNodes = NULL;
}
// copy data
if (node) {
*node = listNode->node;
}
// attach to available pool
listNode->prev = NULL;
listNode->next = list->availNodes;
list->availNodes = listNode;
/*listNode->node.nodeId = 0;IMMUTABLE*/
listNode->node.numBytes = 0;
listNode->node.bytes = NULL;
} else {
assert(list->queuedNodes == NULL);
}
return found ? 0 : -1;
}
static long playq_remove(PlayQueue_s *_this, INOUT PlayNode_s *node) {
PQList_s *list = (PQList_s *)(_this->_internal);
PQListNode_s *listNode = list->queuedNodes;
bool found = _iterate_list_and_find_node(&listNode, node->nodeId);
if (found) {
// reset head
if (listNode->next == NULL) {
list->queuedHead = listNode->prev;
}
// detach listNode from queued list ...
if (listNode->prev) {
listNode->prev->next = listNode->next;
if (listNode->next) {
listNode->next->prev = listNode->prev;
}
} else {
list->queuedNodes = listNode->next;
if (listNode->next) {
listNode->next->prev = NULL;
}
}
*node = listNode->node; // copy data
listNode->next = list->availNodes;
listNode->prev = NULL;
list->availNodes = listNode;
/*listNode->node.nodeId = 0;IMMUTABLE*/
listNode->node.numBytes = 0;
listNode->node.bytes = NULL;
}
return found ? 0 : -1;
}
static void playq_drain(PlayQueue_s *_this) {
long err = 0;
do {
err = _this->Dequeue(_this, NULL);
} while (err == 0);
}
static long playq_getHead(PlayQueue_s *_this, OUTPARM PlayNode_s *node) {
long err = 0;
PQList_s *list = (PQList_s *)(_this->_internal);
if (list->queuedHead) {
*node = list->queuedHead->node;
} else {
*node = (PlayNode_s){ 0 };
err = -1;
}
return err;
}
static long playq_get(PlayQueue_s *_this, OUTPARM PlayNode_s *node) {
long err = 0;
PQList_s *list = (PQList_s *)(_this->_internal);
PQListNode_s *listNode = list->queuedNodes;
_iterate_list_and_find_node(&listNode, node->nodeId);
if (listNode) {
*node = listNode->node;
} else {
*node = (PlayNode_s){ 0 };
err = -1;
}
return err;
}
static bool playq_canEnqueue(PlayQueue_s *_this) {
PQList_s *list = (PQList_s *)(_this->_internal);
return (list->availNodes != NULL);
}
// ----------------------------------------------------------------------------
void playq_destroyPlayQueue(INOUT PlayQueue_s **queue) {
if (!(*queue)) {
return;
}
if ((*queue)->Drain) {
(*queue)->Drain(*queue);
}
PQList_s *list = (PQList_s *)((*queue)->_internal);
if (list) {
PQListNode_s *node = list->availNodes;
while (node) {
PQListNode_s *p = node;
node = node->next;
FREE(p);
}
}
FREE(list);
FREE(*queue);
}
PlayQueue_s *playq_createPlayQueue(const long *nodeIdPtr, unsigned long numBuffers) {
PlayQueue_s *playq = NULL;
assert(numBuffers <= MAX_PLAYQ_BUFFERS);
do {
playq = CALLOC(1, sizeof(PlayQueue_s));
if (!playq) {
ERRLOG("no memory");
break;
}
PQList_s *list = CALLOC(1, sizeof(PQList_s));
playq->_internal = list;
if (!list) {
ERRLOG("no memory");
break;
}
bool allocSuccess = true;
for (unsigned long i=0; i<numBuffers; i++) {
PQListNode_s *listNode = CALLOC(1, sizeof(PQListNode_s));
LOG("CREATING PlayNode_s node ID: %ld", nodeIdPtr[i]);
listNode->node.nodeId = nodeIdPtr[i];
if (!listNode) {
ERRLOG("no memory");
allocSuccess = false;
break;
}
listNode->next = list->availNodes;
list->availNodes = listNode;
}
if (!allocSuccess) {
break;
}
playq->Enqueue = &playq_enqueue;
playq->Dequeue = &playq_dequeue;
playq->Remove = &playq_remove;
playq->Drain = &playq_drain;
playq->GetHead = &playq_getHead;
playq->Get = &playq_get;
playq->CanEnqueue = &playq_canEnqueue;
return playq;
} while (0);
if (playq) {
playq_destroyPlayQueue(&playq);
}
return NULL;
}
#define SELF_TEST 0
#if SELF_TEST
bool do_logging = true;
FILE *error_log = NULL;
static void _test_creation(void) {
LOG("begin test");
const unsigned long maxNodes = 8;
long nodeIds[maxNodes];
for (unsigned long i=0; i<maxNodes; i++) {
nodeIds[i] = i+42;
}
PlayQueue_s *pq = playq_createPlayQueue(nodeIds, maxNodes);
assert(pq != NULL);
playq_destroyPlayQueue(&pq);
assert(pq == NULL);
}
static void _test_internal_list_creation_integrity(void) {
LOG("begin test");
const unsigned long maxNodes = 8;
long nodeIds[maxNodes];
for (unsigned long i=0; i<maxNodes; i++) {
nodeIds[i] = i+42;
}
PlayQueue_s *pq = playq_createPlayQueue(nodeIds, maxNodes);
assert(pq != NULL);
assert(pq->_internal != NULL);
PQList_s *list = (PQList_s *)(pq->_internal);
assert(list->availNodes);
assert(list->queuedNodes == NULL);
assert(list->queuedHead == NULL);
PQListNode_s *listNode = list->availNodes;
unsigned int count = 0;
while (listNode) {
listNode = listNode->next;
++count;
}
assert (count == maxNodes);
playq_destroyPlayQueue(&pq);
assert(pq == NULL);
}
static void _test_enqueue_dequeue(void) {
LOG("begin test");
const unsigned long maxNodes = 4;
long nodeIds[maxNodes];
for (unsigned long i=0; i<maxNodes; i++) {
nodeIds[i] = i+42;
}
PlayQueue_s *pq = playq_createPlayQueue(nodeIds, maxNodes);
assert(pq != NULL);
uint8_t *bytesPtr = (uint8_t *)&bytesPtr;
unsigned long numBytes = 42;
PQList_s *list = (PQList_s *)(pq->_internal);
assert(list->availNodes);
assert(list->queuedNodes == NULL);
assert(list->queuedHead == NULL);
long err = 0;
PlayNode_s node0 = {
.numBytes = numBytes++,
.bytes = bytesPtr++,
};
err = pq->Enqueue(pq, &node0);
assert(err == 0);
assert(list->queuedNodes);
assert(list->queuedHead);
PlayNode_s node1 = {
.numBytes = numBytes++,
.bytes = bytesPtr++,
};
err = pq->Enqueue(pq, &node1);
assert(err == 0);
PlayNode_s node2 = {
.numBytes = numBytes++,
.bytes = bytesPtr++,
};
err = pq->Enqueue(pq, &node2);
assert(err == 0);
PlayNode_s node3 = {
.numBytes = numBytes++,
.bytes = bytesPtr++,
};
err = pq->Enqueue(pq, &node3);
assert(err == 0);
assert(list->availNodes == NULL);
// test over-enqueue
PlayNode_s node4 = {
.numBytes = numBytes++,
.bytes = bytesPtr++,
};
err = pq->Enqueue(pq, &node4);
assert(err != 0 && "this should fail");
// check internal list integrity forward
PQListNode_s *listNode = list->queuedNodes;
assert(listNode->node.nodeId == node3.nodeId);
assert(listNode->node.numBytes == node3.numBytes);
assert(listNode->node.bytes == node3.bytes);
listNode = listNode->next;
assert(listNode->node.nodeId == node2.nodeId);
assert(listNode->node.numBytes == node2.numBytes);
assert(listNode->node.bytes == node2.bytes);
listNode = listNode->next;
assert(listNode->node.nodeId == node1.nodeId);
assert(listNode->node.numBytes == node1.numBytes);
assert(listNode->node.bytes == node1.bytes);
listNode = listNode->next;
assert(listNode == list->queuedHead);
assert(listNode->next == NULL);
assert(listNode->node.nodeId == node0.nodeId);
assert(listNode->node.numBytes == node0.numBytes);
assert(listNode->node.bytes == node0.bytes);
// check internal list integrity backward
listNode = listNode->prev;
PQListNode_s *prevHead1 = listNode;
assert(listNode->node.nodeId == node1.nodeId);
assert(listNode->node.numBytes == node1.numBytes);
assert(listNode->node.bytes == node1.bytes);
listNode = listNode->prev;
PQListNode_s *prevHead2 = listNode;
assert(listNode->node.nodeId == node2.nodeId);
assert(listNode->node.numBytes == node2.numBytes);
assert(listNode->node.bytes == node2.bytes);
listNode = listNode->prev;
PQListNode_s *prevHead3 = listNode;
assert(listNode->node.nodeId == node3.nodeId);
assert(listNode->node.numBytes == node3.numBytes);
assert(listNode->node.bytes == node3.bytes);
assert(listNode == list->queuedNodes);
assert(prevHead3 == list->queuedNodes);
assert(listNode->prev == NULL);
// test one dequeue
PlayNode_s dqNode = { 0 };
err = pq->Dequeue(pq, &dqNode);
assert(err == 0);
assert(dqNode.nodeId == node0.nodeId);
assert(dqNode.numBytes == node0.numBytes);
assert(dqNode.bytes == node0.bytes);
assert(list->queuedHead == prevHead1);
assert(list->availNodes != NULL);
assert(list->availNodes->next == NULL);
// test successful re-enqueue of node4
err = pq->Enqueue(pq, &node4);
assert(err == 0);
assert(list->availNodes == NULL);
assert(list->queuedHead == prevHead1);
assert(list->queuedNodes != prevHead3);
// test dequeue all the things ...
err = pq->Dequeue(pq, &dqNode);
assert(err == 0);
assert(dqNode.nodeId == node1.nodeId);
assert(dqNode.numBytes == node1.numBytes);
assert(dqNode.bytes == node1.bytes);
assert(list->queuedHead == prevHead2);
assert(list->availNodes != NULL);
assert(list->availNodes->next == NULL);
err = pq->Dequeue(pq, &dqNode);
assert(err == 0);
assert(dqNode.nodeId == node2.nodeId);
assert(dqNode.numBytes == node2.numBytes);
assert(dqNode.bytes == node2.bytes);
assert(list->queuedHead == prevHead3);
assert(list->availNodes != NULL);
assert(list->availNodes->next != NULL);
assert(list->availNodes->next->next == NULL);
err = pq->Dequeue(pq, &dqNode);
assert(err == 0);
assert(dqNode.nodeId == node3.nodeId);
assert(dqNode.numBytes == node3.numBytes);
assert(dqNode.bytes == node3.bytes);
assert(list->availNodes != NULL);
assert(list->availNodes->next != NULL);
assert(list->availNodes->next->next != NULL);
assert(list->availNodes->next->next->next == NULL);
err = pq->Dequeue(pq, &dqNode);
assert(err == 0);
assert(dqNode.nodeId == node4.nodeId);
assert(dqNode.numBytes == node4.numBytes);
assert(dqNode.bytes == node4.bytes);
assert(list->queuedHead == NULL);
assert(list->queuedNodes == NULL);
assert(list->availNodes != NULL);
assert(list->availNodes->next != NULL);
assert(list->availNodes->next->next != NULL);
assert(list->availNodes->next->next->next != NULL);
assert(list->availNodes->next->next->next->next == NULL);
// test over-dequeue
err = pq->Dequeue(pq, &dqNode);
assert (err != 0 && "cannot dequeue with nothing there");
assert(list->queuedNodes == NULL);
assert(list->queuedHead == NULL);
// cleanup
playq_destroyPlayQueue(&pq);
assert(pq == NULL);
}
static void _test_remove_head_of_queue(void) {
LOG("begin test");
const unsigned long maxNodes = 4;
long nodeIds[maxNodes];
for (unsigned long i=0; i<maxNodes; i++) {
nodeIds[i] = i+42;
}
PlayQueue_s *pq = playq_createPlayQueue(nodeIds, maxNodes);
assert(pq != NULL);
uint8_t *bytesPtr = (uint8_t *)&bytesPtr;
unsigned long numBytes = 42;
long err = 0;
PQList_s *list = (PQList_s *)(pq->_internal);
assert(list->availNodes != NULL);
assert(list->queuedNodes == NULL);
assert(list->queuedHead == NULL);
PlayNode_s node0 = {
.numBytes = numBytes++,
.bytes = bytesPtr++,
};
err = pq->Enqueue(pq, &node0);
assert(err == 0);
assert(list->queuedNodes != NULL);
assert(list->queuedHead != NULL);
PQListNode_s *listHead0 = list->queuedHead;
PlayNode_s node1 = {
.numBytes = numBytes++,
.bytes = bytesPtr++,
};
err = pq->Enqueue(pq, &node1);
assert(err == 0);
PlayNode_s node2 = {
.numBytes = numBytes++,
.bytes = bytesPtr++,
};
err = pq->Enqueue(pq, &node2);
assert(err == 0);
PlayNode_s node3 = {
.numBytes = numBytes++,
.bytes = bytesPtr++,
};
err = pq->Enqueue(pq, &node3);
assert(err == 0);
assert(list->queuedHead == listHead0);
// dequeue head node using remove
assert(list->availNodes == NULL);
PlayNode_s dqNode = {
.nodeId = node0.nodeId,
};
err = pq->Remove(pq, &dqNode);
assert(dqNode.nodeId == node0.nodeId);
assert(dqNode.numBytes == node0.numBytes);
assert(dqNode.bytes == node0.bytes);
// check integrity of inner list
assert(list->availNodes != NULL);
assert(list->availNodes == listHead0);
assert(list->availNodes->next == NULL);
assert(list->availNodes->prev == NULL);
assert(list->queuedNodes != NULL);
assert(list->queuedHead != NULL);
PQListNode_s *listNode = list->queuedNodes;
assert(listNode->prev == NULL);
assert(listNode->next->prev == listNode);
listNode = listNode->next;
assert(listNode->next->prev == listNode);
listNode = listNode->next;
assert(listNode->next == NULL);
assert(list->queuedHead == listNode);
// cleanup
playq_destroyPlayQueue(&pq);
assert(pq == NULL);
}
static void _test_remove_tail_of_queue(void) {
LOG("begin test");
const unsigned long maxNodes = 4;
long nodeIds[maxNodes];
for (unsigned long i=0; i<maxNodes; i++) {
nodeIds[i] = i+42;
}
PlayQueue_s *pq = playq_createPlayQueue(nodeIds, maxNodes);
assert(pq != NULL);
uint8_t *bytesPtr = (uint8_t *)&bytesPtr;
unsigned long numBytes = 42;
long err = 0;
PQList_s *list = (PQList_s *)(pq->_internal);
assert(list->availNodes != NULL);
assert(list->queuedNodes == NULL);
assert(list->queuedHead == NULL);
PlayNode_s node0 = {
.numBytes = numBytes++,
.bytes = bytesPtr++,
};
err = pq->Enqueue(pq, &node0);
assert(err == 0);
assert(list->queuedNodes != NULL);
assert(list->queuedHead != NULL);
PQListNode_s *listHead0 = list->queuedHead;
PlayNode_s node1 = {
.numBytes = numBytes++,
.bytes = bytesPtr++,
};
err = pq->Enqueue(pq, &node1);
assert(err == 0);
PlayNode_s node2 = {
.numBytes = numBytes++,
.bytes = bytesPtr++,
};
err = pq->Enqueue(pq, &node2);
assert(err == 0);
PlayNode_s node3 = {
.numBytes = numBytes++,
.bytes = bytesPtr++,
};
err = pq->Enqueue(pq, &node3);
assert(err == 0);
PQListNode_s *listHead3 = list->queuedNodes;
assert(list->queuedHead == listHead0);
// dequeue head node using remove
assert(list->availNodes == NULL);
PlayNode_s dqNode = {
.nodeId = node3.nodeId,
};
err = pq->Remove(pq, &dqNode);
assert(dqNode.nodeId == node3.nodeId);
assert(dqNode.numBytes == node3.numBytes);
assert(dqNode.bytes == node3.bytes);
// check integrity of inner list
assert(list->availNodes != NULL);
assert(list->availNodes == listHead3);
assert(list->availNodes->prev == NULL);
assert(list->availNodes->next == NULL);
assert(list->queuedNodes != NULL);
assert(list->queuedHead != NULL);
assert(list->queuedHead == listHead0);
PQListNode_s *listNode = list->queuedNodes;
assert(listNode->prev == NULL);
assert(listNode->next->prev == listNode);
listNode = listNode->next;
assert(listNode->next->prev == listNode);
listNode = listNode->next;
assert(listNode->next == NULL);
assert(list->queuedHead == listNode);
// cleanup
playq_destroyPlayQueue(&pq);
assert(pq == NULL);
}
static void _test_remove_middle_of_queue(void) {
LOG("begin test");
const unsigned long maxNodes = 4;
long nodeIds[maxNodes];
for (unsigned long i=0; i<maxNodes; i++) {
nodeIds[i] = i+42;
}
PlayQueue_s *pq = playq_createPlayQueue(nodeIds, maxNodes);
assert(pq != NULL);
uint8_t *bytesPtr = (uint8_t *)&bytesPtr;
unsigned long numBytes = 42;
long err = 0;
PQList_s *list = (PQList_s *)(pq->_internal);
assert(list->availNodes != NULL);
assert(list->queuedNodes == NULL);
assert(list->queuedHead == NULL);
PlayNode_s node0 = {
.numBytes = numBytes++,
.bytes = bytesPtr++,
};
err = pq->Enqueue(pq, &node0);
assert(err == 0);
assert(list->queuedNodes != NULL);
assert(list->queuedHead != NULL);
PQListNode_s *listHead0 = list->queuedHead;
PlayNode_s node1 = {
.numBytes = numBytes++,
.bytes = bytesPtr++,
};
err = pq->Enqueue(pq, &node1);
assert(err == 0);
PlayNode_s node2 = {
.numBytes = numBytes++,
.bytes = bytesPtr++,
};
err = pq->Enqueue(pq, &node2);
assert(err == 0);
PQListNode_s *listHead2 = list->queuedNodes;
PlayNode_s node3 = {
.numBytes = numBytes++,
.bytes = bytesPtr++,
};
err = pq->Enqueue(pq, &node3);
assert(err == 0);
assert(list->queuedHead == listHead0);
// dequeue head node using remove
assert(list->availNodes == NULL);
PlayNode_s dqNode = {
.nodeId = node2.nodeId,
};
err = pq->Remove(pq, &dqNode);
assert(dqNode.nodeId == node2.nodeId);
assert(dqNode.numBytes == node2.numBytes);
assert(dqNode.bytes == node2.bytes);
// check integrity of inner list
assert(list->availNodes != NULL);
assert(list->availNodes == listHead2);
assert(list->availNodes->prev == NULL);
assert(list->availNodes->next == NULL);
assert(list->queuedNodes != NULL);
assert(list->queuedHead != NULL);
assert(list->queuedHead == listHead0);
PQListNode_s *listNode = list->queuedNodes;
assert(listNode->prev == NULL);
assert(listNode->next->prev == listNode);
listNode = listNode->next;
assert(listNode->next->prev == listNode);
listNode = listNode->next;
assert(listNode->next == NULL);
assert(list->queuedHead == listNode);
// cleanup
playq_destroyPlayQueue(&pq);
assert(pq == NULL);
}
int main(int argc, char **argv) {
#warning use Valgrind to check proper memory management
error_log = stdout;
LOG("beginning tests");
_test_creation();
_test_internal_list_creation_integrity();
_test_enqueue_dequeue();
_test_remove_head_of_queue();
_test_remove_tail_of_queue();
_test_remove_middle_of_queue();
LOG("all tests successful");
return 0;
}
#endif

View File

@ -1,63 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
/*
* A simple audio buffer play queue.
*
* WARNING : non-thread-safe API ... locking is callee's responsibility (if needed)
*
*/
#ifndef _PLAYQUEUE_H_
#define _PLAYQUEUE_H_
#define MAX_PLAYQ_BUFFERS 16
#define INVALID_NODE_ID INT_MIN
typedef struct PlayNode_s {
long nodeId;
unsigned long numBytes;
uint8_t *bytes;
} PlayNode_s;
typedef struct PlayQueue_s {
PRIVATE void *_internal;
// enqueues a node (IN : numBytes, bytes OUT : nodeId)
long (*Enqueue)(struct PlayQueue_s *_this, INOUT PlayNode_s *node);
// dequeues the head of the queue (OUT : full PlayNode_s data if param is non-null)
long (*Dequeue)(struct PlayQueue_s *_this, OUTPARM PlayNode_s *node);
// finds and removes a specific node (IN : nodeId OUT : full PlayNode_s data)
long (*Remove)(struct PlayQueue_s *_this, INOUT PlayNode_s *node);
// removes all nodes from the queue
void (*Drain)(struct PlayQueue_s *_this);
// gets the head node (OUT : full PlayNode_s data)
long (*GetHead)(struct PlayQueue_s *_this, OUTPARM PlayNode_s *node);
// gets a reference to a specific node (IN : nodeId OUT : full PlayNode_s data)
long (*Get)(struct PlayQueue_s *_this, INOUT PlayNode_s *node);
// true if we can enqueue moar data
bool (*CanEnqueue)(struct PlayQueue_s *_this);
} PlayQueue_s;
// create a play queue object
PlayQueue_s *playq_createPlayQueue(const long *nodeIdPtr, unsigned long numBuffers);
// destroy a play queue object
void playq_destroyPlayQueue(INOUT PlayQueue_s **queue);
#endif /* whole file */

View File

@ -1,642 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
// soundcore OpenAL backend -- streaming audio
#include "common.h"
#ifdef __APPLE__
# import <OpenAL/al.h>
# import <OpenAL/alc.h>
#else
# include <AL/al.h>
# include <AL/alc.h>
# include <AL/alext.h>
#endif
#include "audio/alhelpers.h"
#include "playqueue.h"
#include "uthash.h"
#define DEBUG_OPENAL 0
#if DEBUG_OPENAL
# define OPENAL_LOG(...) LOG(__VA_ARGS__)
#else
# define OPENAL_LOG(...)
#endif
#define OPENAL_NUM_BUFFERS 4
typedef struct ALVoice {
ALuint source;
ALuint buffers[OPENAL_NUM_BUFFERS];
// playing data
PlayQueue_s *playq;
ALint _queued_total_bytes; // a maximum estimate -- actual value depends on OpenAL query
// working data buffer
ALbyte *data;
ALsizei index; // working buffer byte index
ALsizei buffersize; // working buffer size (and OpenAL buffersize)
ALsizei replay_index;
// sample parameters
ALenum format;
ALenum channels;
ALenum type;
ALuint rate;
} ALVoice;
typedef struct ALVoices {
const ALuint source;
ALVoice *voice;
UT_hash_handle hh;
} ALVoices;
static ALVoices *voices = NULL;
static AudioBackend_s openal_audio_backend = { { 0 } };
// ----------------------------------------------------------------------------
// AudioBuffer_s processing routines
static void _playq_removeNode(ALVoice *voice, PlayNode_s *playNode) {
long err = voice->playq->Remove(voice->playq, playNode);
assert(err == 0);
voice->_queued_total_bytes -= playNode->numBytes;
assert(voice->_queued_total_bytes >= 0);
}
static long _ALProcessPlayBuffers(ALVoice *voice, ALuint *bytes_queued) {
long err = 0;
*bytes_queued = 0;
do {
ALint processed = 0;
alGetSourcei(voice->source, AL_BUFFERS_PROCESSED, &processed);
if ((err = alGetError()) != AL_NO_ERROR) {
ERRLOG("OOPS, error in checking processed buffers : 0x%08lx", err);
break;
}
while (processed > 0) {
--processed;
ALuint bufid = 0;
alSourceUnqueueBuffers(voice->source, 1, &bufid);
if ((err = alGetError()) != AL_NO_ERROR) {
ERRLOG("OOPS, OpenAL error dequeuing buffer : 0x%08lx", err);
break;
}
OPENAL_LOG("Attempting to dequeue %u ...", bufid);
PlayNode_s playNode = {
.nodeId = bufid,
};
err = voice->playq->Get(voice->playq, &playNode);
if (err) {
ERRLOG("OOPS, OpenAL bufid %u not found in playlist...", bufid);
} else {
_playq_removeNode(voice, &playNode);
}
}
ALint play_offset = 0;
alGetSourcei(voice->source, AL_BYTE_OFFSET, &play_offset);
if ((err = alGetError()) != AL_NO_ERROR) {
ERRLOG("OOPS, alGetSourcei AL_BYTE_OFFSET : 0x%08lx", err);
break;
}
assert((play_offset >= 0)/* && (play_offset < voice->buffersize)*/);
long q = voice->_queued_total_bytes/* + voice->index*/ - play_offset;
if (q >= 0) {
*bytes_queued = (ALuint)q;
}
} while (0);
return err;
}
// returns queued+working sound buffer size in bytes
static long ALGetPosition(AudioBuffer_s *_this, OUTPARM unsigned long *bytes_queued) {
*bytes_queued = 0;
long err = 0;
do {
ALVoice *voice = (ALVoice*)_this->_internal;
ALuint queued = 0;
long err = _ALProcessPlayBuffers(voice, &queued);
if (err) {
break;
}
#if DEBUG_OPENAL
static int last_queued = 0;
if (queued != last_queued) {
last_queued = queued;
OPENAL_LOG("OpenAL bytes queued : %u", queued);
}
#endif
*bytes_queued = queued + voice->index;
} while (0);
return err;
}
static long ALLockBuffer(AudioBuffer_s *_this, unsigned long write_bytes, INOUT int16_t **audio_ptr, OUTPARM unsigned long *audio_bytes) {
*audio_bytes = 0;
*audio_ptr = NULL;
long err = 0;
do {
ALVoice *voice = (ALVoice*)_this->_internal;
if (write_bytes == 0) {
write_bytes = voice->buffersize;
}
ALuint bytes_queued = 0;
err = _ALProcessPlayBuffers(voice, &bytes_queued);
if (err) {
break;
}
if ((bytes_queued == 0) && (voice->index == 0)) {
LOG("Buffer underrun ... queuing quiet samples ...");
int quiet_size = voice->buffersize>>2/* 1/4 buffer */;
memset(voice->data, 0x0, quiet_size);
voice->index += quiet_size;
}
#if 0
else if (bytes_queued + voice->index < (voice->buffersize>>3)/* 1/8 buffer */)
{
OPENAL_LOG("Potential underrun ...");
}
#endif
ALsizei remaining = voice->buffersize - voice->index;
if (write_bytes > remaining) {
write_bytes = remaining;
}
*audio_ptr = (int16_t *)(voice->data+voice->index);
*audio_bytes = write_bytes;
} while (0);
return err;
}
static long _ALSubmitBufferToOpenAL(ALVoice *voice) {
long err = 0;
do {
// Micro-manage play queue locally to understand the total bytes-in-play
PlayNode_s playNode = {
.nodeId = INVALID_NODE_ID,
.numBytes = voice->index,
.bytes = (uint8_t *)(voice->data),
};
err = voice->playq->Enqueue(voice->playq, &playNode);
if (err) {
break;
}
voice->_queued_total_bytes += voice->index;
voice->index = 0;
assert(voice->_queued_total_bytes > 0);
OPENAL_LOG("Enqueing OpenAL buffer %ld (%lu bytes) at %p", playNode.nodeId, playNode.numBytes, playNode.bytes);
alBufferData(playNode.nodeId, voice->format, playNode.bytes, playNode.numBytes, voice->rate);
if ((err = alGetError()) != AL_NO_ERROR) {
_playq_removeNode(voice, &playNode);
ERRLOG("OOPS, Error alBufferData : 0x%08lx", err);
break;
}
ALuint nodeId = (ALuint)playNode.nodeId;
alSourceQueueBuffers(voice->source, 1, &nodeId);
if ((err = alGetError()) != AL_NO_ERROR) {
_playq_removeNode(voice, &playNode);
ERRLOG("OOPS, Error buffering data : 0x%08lx", err);
break;
}
ALint state = 0;
alGetSourcei(voice->source, AL_SOURCE_STATE, &state);
if ((err = alGetError()) != AL_NO_ERROR) {
ERRLOG("OOPS, Error checking source state : 0x%08lx", err);
break;
}
if ((state != AL_PLAYING) && (state != AL_PAUSED)) {
// 2013/11/17 NOTE : alSourcePlay() is expensive and causes audio artifacts, only invoke if needed
LOG("Restarting playback (was 0x%08x) ...", state);
alSourcePlay(voice->source);
if ((err = alGetError()) != AL_NO_ERROR) {
LOG("Error starting playback : 0x%08lx", err);
break;
}
}
} while (0);
return err;
}
static long ALUnlockBuffer(AudioBuffer_s *_this, unsigned long audio_bytes) {
long err = 0;
do {
ALVoice *voice = (ALVoice*)_this->_internal;
ALuint bytes_queued = 0;
err = _ALProcessPlayBuffers(voice, &bytes_queued);
if (err) {
break;
}
voice->index += audio_bytes;
if (voice->index >= voice->buffersize) {
assert((voice->index == voice->buffersize) && "OOPS, detected an actual overflow in queued sound data");
}
if (bytes_queued >= (voice->buffersize>>2)/*quarter buffersize*/) {
// keep accumulating data into working buffer
break;
}
if (! (voice->playq->CanEnqueue(voice->playq)) ) {
OPENAL_LOG("no free audio buffers"); // keep accumulating ...
break;
}
// Submit working buffer to OpenAL
err = _ALSubmitBufferToOpenAL(voice);
} while (0);
return err;
}
#if 0
// HACK Part I : done once for mockingboard that has semiauto repeating phonemes ...
static long ALUnlockStaticBuffer(AudioBuffer_s *_this, unsigned long audio_bytes) {
ALVoice *voice = (ALVoice*)_this->_internal;
voice->replay_index = (ALsizei)audio_bytes;
return 0;
}
// HACK Part II : replay mockingboard phoneme ...
static long ALReplay(AudioBuffer_s *_this) {
ALVoice *voice = (ALVoice*)_this->_internal;
voice->index = voice->replay_index;
long err = _ALSubmitBufferToOpenAL(voice);
return err;
}
#endif
static long ALGetStatus(AudioBuffer_s *_this, OUTPARM unsigned long *status) {
*status = -1;
long err = 0;
do {
ALVoice* voice = (ALVoice*)_this->_internal;
ALint state = 0;
alGetSourcei(voice->source, AL_SOURCE_STATE, &state);
if ((err = alGetError()) != AL_NO_ERROR) {
ERRLOG("OOPS, Error checking source state : 0x%08lx", err);
break;
}
if ((state == AL_PLAYING) || (state == AL_PAUSED)) {
*status = AUDIO_STATUS_PLAYING;
} else {
*status = AUDIO_STATUS_NOTPLAYING;
}
} while (0);
return err;
}
// ----------------------------------------------------------------------------
// ALVoice is the AudioBuffer_s->_internal
static void _openal_destroyVoice(ALVoice *voice) {
alDeleteSources(1, &voice->source);
if (alGetError() != AL_NO_ERROR) {
ERRLOG("OOPS, Failed to delete source");
}
if (voice->data) {
FREE(voice->data);
}
for (unsigned int i=0; i<OPENAL_NUM_BUFFERS; i++) {
alDeleteBuffers(1, voice->buffers);
if (alGetError() != AL_NO_ERROR) {
ERRLOG("OOPS, Failed to delete object IDs");
}
}
playq_destroyPlayQueue(&(voice->playq));
memset(voice, 0, sizeof(*voice));
FREE(voice);
}
static ALVoice *_openal_createVoice(unsigned long numChannels) {
ALVoice *voice = NULL;
do {
voice = CALLOC(1, sizeof(*voice));
if (voice == NULL) {
ERRLOG("OOPS, Out of memory!");
break;
}
alGenBuffers(OPENAL_NUM_BUFFERS, voice->buffers);
if (alGetError() != AL_NO_ERROR) {
ERRLOG("OOPS, Could not create buffers");
break;
}
alGenSources(1, &voice->source);
if (alGetError() != AL_NO_ERROR) {
ERRLOG("OOPS, Could not create source");
break;
}
// Set parameters so mono sources play out the front-center speaker and won't distance attenuate.
alSource3i(voice->source, AL_POSITION, 0, 0, -1);
if (alGetError() != AL_NO_ERROR) {
ERRLOG("OOPS, Could not set AL_POSITION source parameter");
break;
}
alSourcei(voice->source, AL_SOURCE_RELATIVE, AL_TRUE);
if (alGetError() != AL_NO_ERROR) {
ERRLOG("OOPS, Could not set AL_SOURCE_RELATIVE source parameter");
break;
}
alSourcei(voice->source, AL_ROLLOFF_FACTOR, 0);
if (alGetError() != AL_NO_ERROR) {
ERRLOG("OOPS, Could not set AL_ROLLOFF_FACTOR source parameter");
break;
}
#if 0
alSourcei(voice->source, AL_STREAMING, AL_TRUE);
if (alGetError() != AL_NO_ERROR) {
ERRLOG("OOPS, Could not set AL_STREAMING source parameter");
break;
}
#endif
long longBuffers[OPENAL_NUM_BUFFERS];
for (unsigned int i=0; i<OPENAL_NUM_BUFFERS; i++) {
longBuffers[i] = (long)(voice->buffers[i]);
}
voice->playq = playq_createPlayQueue(longBuffers, OPENAL_NUM_BUFFERS);
if (!voice->playq) {
ERRLOG("OOPS, Not enough memory for PlayQueue");
break;
}
voice->rate = openal_audio_backend.systemSettings.sampleRateHz;
// Emulator supports only mono and stereo
if (numChannels == 2) {
voice->format = AL_FORMAT_STEREO16;
} else {
voice->format = AL_FORMAT_MONO16;
}
/* Allocate enough space for the temp buffer, given the format */
assert(numChannels == 1 || numChannels == 2);
unsigned long maxSamples = openal_audio_backend.systemSettings.monoBufferSizeSamples * numChannels;
voice->buffersize = maxSamples * openal_audio_backend.systemSettings.bytesPerSample;
voice->data = CALLOC(1, voice->buffersize);
if (voice->data == NULL) {
ERRLOG("OOPS, Error allocating %d bytes", voice->buffersize);
break;
}
LOG("\tRate : 0x%08x", voice->rate);
LOG("\tFormat : 0x%08x", voice->format);
LOG("\tbuffersize : %d", voice->buffersize);
return voice;
} while(0);
// ERR
if (voice) {
_openal_destroyVoice(voice);
}
return NULL;
}
// ----------------------------------------------------------------------------
static long openal_destroySoundBuffer(const struct AudioContext_s *sound_system, INOUT AudioBuffer_s **soundbuf_struct) {
if (!*soundbuf_struct) {
// already dealloced
return 0;
}
LOG("openal_destroySoundBuffer ...");
ALVoice *voice = (ALVoice *)((*soundbuf_struct)->_internal);
ALint source = voice->source;
_openal_destroyVoice(voice);
ALVoices *vnode = NULL;
HASH_FIND_INT(voices, &source, vnode);
if (vnode) {
HASH_DEL(voices, vnode);
FREE(vnode);
}
FREE(*soundbuf_struct);
return 0;
}
static long openal_createSoundBuffer(const AudioContext_s *audio_context, INOUT AudioBuffer_s **soundbuf_struct) {
LOG("openal_createSoundBuffer ...");
assert(*soundbuf_struct == NULL);
ALVoice *voice = NULL;
do {
ALCcontext *ctx = (ALCcontext*)(audio_context->_internal);
assert(ctx != NULL);
if ((voice = _openal_createVoice(NUM_CHANNELS)) == NULL) {
ERRLOG("OOPS, Cannot create new voice");
break;
}
ALVoices immutableNode = { /*const*/.source = voice->source };
ALVoices *vnode = CALLOC(1, sizeof(ALVoices));
if (!vnode) {
ERRLOG("OOPS, Not enough memory");
break;
}
memcpy(vnode, &immutableNode, sizeof(ALVoices));
vnode->voice = voice;
HASH_ADD_INT(voices, source, vnode);
if ((*soundbuf_struct = CALLOC(1, sizeof(AudioBuffer_s))) == NULL) {
ERRLOG("OOPS, Not enough memory");
break;
}
(*soundbuf_struct)->_internal = voice;
(*soundbuf_struct)->GetCurrentPosition = &ALGetPosition;
(*soundbuf_struct)->Lock = &ALLockBuffer;
(*soundbuf_struct)->Unlock = &ALUnlockBuffer;
(*soundbuf_struct)->GetStatus = &ALGetStatus;
// mockingboard-specific hacks
//(*soundbuf_struct)->UnlockStaticBuffer = &ALUnlockStaticBuffer;
//(*soundbuf_struct)->Replay = &ALReplay;
return 0;
} while(0);
if (*soundbuf_struct) {
openal_destroySoundBuffer(audio_context, soundbuf_struct);
} else if (voice) {
_openal_destroyVoice(voice);
}
return -1;
}
// ----------------------------------------------------------------------------
static long openal_systemShutdown(INOUT AudioContext_s **audio_context) {
assert(*audio_context != NULL);
ALCcontext *ctx = (ALCcontext*) (*audio_context)->_internal;
assert(ctx != NULL);
(*audio_context)->_internal = NULL;
FREE(*audio_context);
// NOTE : currently assuming just one OpenAL global context
CloseAL();
return 0;
}
static long openal_systemSetup(INOUT AudioContext_s **audio_context) {
assert(*audio_context == NULL);
assert(voices == NULL);
long result = -1;
ALCcontext *ctx = NULL;
// 2015/06/29 these values seem to work well on Linux desktop ... no other OpenAL platform has been tested
openal_audio_backend.systemSettings.sampleRateHz = 22050;
openal_audio_backend.systemSettings.bytesPerSample = 2;
openal_audio_backend.systemSettings.monoBufferSizeSamples = (8*1024);
openal_audio_backend.systemSettings.stereoBufferSizeSamples = openal_audio_backend.systemSettings.monoBufferSizeSamples;
do {
if ((ctx = InitAL()) == NULL) {
// NOTE : currently assuming just one OpenAL global context
ERRLOG("OOPS, OpenAL initialize failed");
break;
}
if (alIsExtensionPresent("AL_SOFT_buffer_samples")) {
LOG("AL_SOFT_buffer_samples supported, good!");
} else {
LOG("WARNING - AL_SOFT_buffer_samples extension not supported... Proceeding anyway...");
}
if ((*audio_context = CALLOC(1, sizeof(AudioContext_s))) == NULL) {
ERRLOG("OOPS, Not enough memory");
break;
}
(*audio_context)->_internal = ctx;
(*audio_context)->CreateSoundBuffer = &openal_createSoundBuffer;
(*audio_context)->DestroySoundBuffer = &openal_destroySoundBuffer;
result = 0;
} while(0);
if (result) {
if (ctx) {
AudioContext_s *ctxPtr = CALLOC(1, sizeof(AudioContext_s));
ctxPtr->_internal = ctx;
openal_systemShutdown(&ctxPtr);
}
assert (*audio_context == NULL);
LOG("OpenAL sound output disabled");
}
return result;
}
static long openal_systemPause(AudioContext_s *audio_context) {
ALVoices *vnode = NULL;
ALVoices *tmp = NULL;
long err = 0;
HASH_ITER(hh, voices, vnode, tmp) {
alSourcePause(vnode->source);
err = alGetError();
if (err != AL_NO_ERROR) {
ERRLOG("OOPS, Failed to pause source : 0x%08lx", err);
}
}
return 0;
}
static long openal_systemResume(AudioContext_s *audio_context) {
ALVoices *vnode = NULL;
ALVoices *tmp = NULL;
long err = 0;
HASH_ITER(hh, voices, vnode, tmp) {
alSourcePlay(vnode->source);
err = alGetError();
if (err != AL_NO_ERROR) {
ERRLOG("OOPS, Failed to pause source : 0x%08lx", err);
}
}
return 0;
}
static void _init_openal(void) {
LOG("Initializing OpenAL sound system");
assert((audio_backend == NULL) && "there can only be one!");
openal_audio_backend.setup = &openal_systemSetup;
openal_audio_backend.shutdown = &openal_systemShutdown;
openal_audio_backend.pause = &openal_systemPause;
openal_audio_backend.resume = &openal_systemResume;
audio_backend = &openal_audio_backend;
}
static __attribute__((constructor)) void __init_openal(void) {
emulator_registerStartupCallback(CTOR_PRIORITY_EARLY, &_init_openal);
}

View File

@ -1,777 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
// soundcore OpenSLES backend -- streaming audio
#include "common.h"
#include <SLES/OpenSLES.h>
#if defined(ANDROID)
# include <SLES/OpenSLES_Android.h>
#else
# error FIXME TODO this currently uses Android BufferQueue extensions...
#endif
#define DEBUG_OPENSL 0
#if DEBUG_OPENSL
# define OPENSL_LOG(...) LOG(__VA_ARGS__)
#else
# define OPENSL_LOG(...)
#endif
#define NUM_CHANNELS 2
typedef struct SLVoice {
void *ctx; // EngineContext_s
// working data buffer
uint8_t *ringBuffer; // ringBuffer of total size : bufferSize+submitSize
unsigned long bufferSize; // ringBuffer non-overflow size
ptrdiff_t writeHead; // head of the writer of ringBuffer (speaker, mockingboard)
unsigned long writeWrapCount; // count of buffer wraps for the writer
unsigned long spinLock; // spinlock around reader variables
ptrdiff_t readHead; // head of the reader of ringBuffer (OpenSLES callback)
unsigned long readWrapCount; // count of buffer wraps for the reader
// next voice
struct SLVoice *next;
} SLVoice;
typedef struct EngineContext_s {
SLObjectItf engineObject;
SLEngineItf engineEngine;
SLObjectItf outputMixObject;
SLObjectItf bqPlayerObject;
SLPlayItf bqPlayerPlay;
SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue;
uint8_t *mixBuf; // mix buffer submitted to OpenSLES
unsigned long submitSize; // buffer size OpenSLES expects/wants
SLVoice *voices;
SLVoice *recycledVoices;
} EngineContext_s;
static AudioBackend_s opensles_audio_backend = { 0 };
// ----------------------------------------------------------------------------
// AudioBuffer_s internal processing routines
// Check and resets underrun condition (readHead has advanced beyond writeHead)
static inline bool _underrun_check_and_manage(SLVoice *voice, OUTPARM unsigned long *workingBytes) {
SPINLOCK_ACQUIRE(&voice->spinLock);
unsigned long readHead = voice->readHead;
unsigned long readWrapCount = voice->readWrapCount;
SPINLOCK_RELINQUISH(&voice->spinLock);
assert(readHead < voice->bufferSize);
assert(voice->writeHead < voice->bufferSize);
bool isUnder = false;
if ( (readWrapCount > voice->writeWrapCount) ||
((readHead >= voice->writeHead) && (readWrapCount == voice->writeWrapCount)) )
{
isUnder = true;
LOG("Buffer underrun ...");
voice->writeHead = readHead;
voice->writeWrapCount = readWrapCount;
}
if (readHead <= voice->writeHead) {
*workingBytes = voice->writeHead - readHead;
} else {
*workingBytes = voice->writeHead + (voice->bufferSize - readHead);
}
return isUnder;
}
// This callback handler is called presumably every time (or just prior to when) a buffer finishes playing and the
// system needs moar data (of the correct buffer size)
static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {
// invariant : can always read submitSize from position of readHead (bufferSize+submitSize)
EngineContext_s *ctx = (EngineContext_s *)context;
SLresult result = SL_RESULT_SUCCESS;
do {
// This is a very simple inline mixer so that we only need one BufferQueue (which works best on low-end Android
// devices
//
// HACK ASSUMPTIONS :
// * max of 2 voices/buffers
// * both buffers contain stereo signed 16bit samples with zero as mid point
// * absolute value of maximum amplitude is less than one half SHRT_MAX (to avoid clipping)
SLVoice *voice0 = ctx->voices;
if (!voice0) {
result = -1;
break;
}
// copy/mix data
memcpy(ctx->mixBuf, voice0->ringBuffer+voice0->readHead, ctx->submitSize);
SLVoice *voice1 = voice0->next;
if (voice1) {
// add second waveform into mix buffer
////if (SIMD_IS_AVAILABLE()) {
#if USE_SIMD
#warning FIXME TODO vectorial code here
#endif
////} else {
uint16_t *mixBuf = (uint16_t *)ctx->mixBuf;
unsigned long submitSize = ctx->submitSize>>1;
for (unsigned long i=0; i<submitSize; i++) {
mixBuf[i] += ((uint16_t *)(voice1->ringBuffer+voice1->readHead))[i];
}
////}
}
// submit data to OpenSLES
result = (*bq)->Enqueue(bq, ctx->mixBuf, ctx->submitSize);
// now manage quiet backfilling and overflow/wrapping ...
memset(voice0->ringBuffer+voice0->readHead, 0x0, ctx->submitSize); // backfill quiet samples
unsigned long newReadHead0 = voice0->readHead + ctx->submitSize;
unsigned long newReadWrapCount0 = voice0->readWrapCount;
if (newReadHead0 >= voice0->bufferSize) {
newReadHead0 = newReadHead0 - voice0->bufferSize;
memset(voice0->ringBuffer+voice0->bufferSize, 0x0, ctx->submitSize); // backfill quiet samples
memset(voice0->ringBuffer, 0x0, newReadHead0);
++newReadWrapCount0;
}
SPINLOCK_ACQUIRE(&voice0->spinLock);
voice0->readHead = newReadHead0;
voice0->readWrapCount = newReadWrapCount0;
SPINLOCK_RELINQUISH(&voice0->spinLock);
if (voice1) {
memset(voice1->ringBuffer+voice1->readHead, 0x0, ctx->submitSize); // backfill quiet samples
unsigned long newReadHead1 = voice1->readHead + ctx->submitSize;
unsigned long newReadWrapCount1 = voice1->readWrapCount;
if (newReadHead1 >= voice1->bufferSize) {
newReadHead1 = newReadHead1 - voice1->bufferSize;
memset(voice1->ringBuffer+voice1->bufferSize, 0x0, ctx->submitSize); // backfill quiet samples
memset(voice1->ringBuffer, 0x0, newReadHead1);
++newReadWrapCount1;
}
SPINLOCK_ACQUIRE(&voice1->spinLock);
voice1->readHead = newReadHead1;
voice1->readWrapCount = newReadWrapCount1;
SPINLOCK_RELINQUISH(&voice1->spinLock);
}
} while (0);
if (result != SL_RESULT_SUCCESS) {
LOG("WARNING: could not enqueue data to OpenSLES!");
(*(ctx->bqPlayerPlay))->SetPlayState(ctx->bqPlayerPlay, SL_PLAYSTATE_STOPPED);
}
}
static long _SLMaybeSubmitAndStart(SLVoice *voice) {
SLuint32 state = 0;
EngineContext_s *ctx = (EngineContext_s *)voice->ctx;
SLresult result = (*(ctx->bqPlayerPlay))->GetPlayState(ctx->bqPlayerPlay, &state);
if (result != SL_RESULT_SUCCESS) {
ERRLOG("OOPS, could not get source state : %lu", (unsigned long)result);
} else {
if ((state != SL_PLAYSTATE_PLAYING) && (state != SL_PLAYSTATE_PAUSED)) {
LOG("FORCING restart audio buffer queue playback ...");
result = (*(ctx->bqPlayerPlay))->SetPlayState(ctx->bqPlayerPlay, SL_PLAYSTATE_PLAYING);
bqPlayerCallback(ctx->bqPlayerBufferQueue, ctx);
}
}
return result;
}
// ----------------------------------------------------------------------------
// AudioBuffer_s public API handlers
// returns queued+working sound buffer size in bytes
static long SLGetPosition(AudioBuffer_s *_this, OUTPARM unsigned long *bytes_queued) {
*bytes_queued = 0;
long err = 0;
do {
SLVoice *voice = (SLVoice*)_this->_internal;
unsigned long workingBytes = 0;
bool underrun = _underrun_check_and_manage(voice, &workingBytes);
//bool overrun = _overrun_check_and_manage(voice);
unsigned long queuedBytes = 0;
if (!underrun) {
//queuedBytes = ctx->submitSize; // assume that there are always about this much actually queued
}
assert(workingBytes <= voice->bufferSize);
*bytes_queued = workingBytes;
} while (0);
return err;
}
static long SLLockBuffer(AudioBuffer_s *_this, unsigned long write_bytes, INOUT int16_t **audio_ptr, OUTPARM unsigned long *audio_bytes) {
*audio_bytes = 0;
*audio_ptr = NULL;
long err = 0;
//OPENSL_LOG("SLLockBuffer request for %ld bytes", write_bytes);
do {
SLVoice *voice = (SLVoice*)_this->_internal;
EngineContext_s *ctx = (EngineContext_s *)voice->ctx;
if (write_bytes == 0) {
LOG("HMMM ... writing full buffer!");
write_bytes = voice->bufferSize;
}
unsigned long workingBytes = 0;
_underrun_check_and_manage(voice, &workingBytes);
unsigned long availableBytes = voice->bufferSize - workingBytes;
assert(workingBytes <= voice->bufferSize);
assert(voice->writeHead < voice->bufferSize);
// TODO FIXME : maybe need to resurrect the 2 inner pointers and foist the responsibility onto the
// speaker/mockingboard modules so we can actually write moar here?
unsigned long writableBytes = MIN( availableBytes, ((voice->bufferSize+ctx->submitSize) - voice->writeHead) );
if (write_bytes > writableBytes) {
OPENSL_LOG("NOTE truncating audio buffer (call again to write complete requested buffer) ...");
write_bytes = writableBytes;
}
*audio_ptr = (int16_t *)(voice->ringBuffer+voice->writeHead);
*audio_bytes = write_bytes;
} while (0);
return err;
}
static long SLUnlockBuffer(AudioBuffer_s *_this, unsigned long audio_bytes) {
long err = 0;
do {
SLVoice *voice = (SLVoice*)_this->_internal;
EngineContext_s *ctx = (EngineContext_s *)voice->ctx;
unsigned long previousWriteHead = voice->writeHead;
voice->writeHead += audio_bytes;
assert((voice->writeHead <= (voice->bufferSize + ctx->submitSize)) && "OOPS, real overflow in queued sound data!");
if (voice->writeHead >= voice->bufferSize) {
// copy data from overflow into beginning of buffer
voice->writeHead = voice->writeHead - voice->bufferSize;
++voice->writeWrapCount;
memcpy(voice->ringBuffer, voice->ringBuffer+voice->bufferSize, voice->writeHead);
} else if (previousWriteHead < ctx->submitSize) {
// copy data in beginning of buffer into overflow position
unsigned long copyNumBytes = MIN(audio_bytes, ctx->submitSize-previousWriteHead);
memcpy(voice->ringBuffer+voice->bufferSize+previousWriteHead, voice->ringBuffer+previousWriteHead, copyNumBytes);
}
err = _SLMaybeSubmitAndStart(voice);
} while (0);
return err;
}
#if 0
// HACK Part I : done once for mockingboard that has semiauto repeating phonemes ...
static long SLUnlockStaticBuffer(AudioBuffer_s *_this, unsigned long audio_bytes) {
SLVoice *voice = (SLVoice*)_this->_internal;
voice->replay_index = audio_bytes;
return 0;
}
// HACK Part II : replay mockingboard phoneme ...
static long SLReplay(AudioBuffer_s *_this) {
SLVoice *voice = (SLVoice*)_this->_internal;
SPINLOCK_ACQUIRE(&voice->spinLock);
voice->readHead = 0;
voice->writeHead = voice->replay_index;
SPINLOCK_RELINQUISH(&voice->spinLock);
long err = _SLMaybeSubmitAndStart(voice);
#warning FIXME TODO ... how do we handle mockingboard for new OpenSLES buffer queue codepath?
return err;
}
#endif
static long SLGetStatus(AudioBuffer_s *_this, OUTPARM unsigned long *status) {
*status = -1;
SLresult result = SL_RESULT_UNKNOWN_ERROR;
do {
SLVoice* voice = (SLVoice*)_this->_internal;
EngineContext_s *ctx = (EngineContext_s *)voice->ctx;
SLuint32 state = 0;
result = (*(ctx->bqPlayerPlay))->GetPlayState(ctx->bqPlayerPlay, &state);
if (result != SL_RESULT_SUCCESS) {
ERRLOG("OOPS, could not get source state : %lu", (unsigned long)result);
break;
}
if ((state == SL_PLAYSTATE_PLAYING) || (state == SL_PLAYSTATE_PAUSED)) {
*status = AUDIO_STATUS_PLAYING;
} else {
*status = AUDIO_STATUS_NOTPLAYING;
}
} while (0);
return (long)result;
}
// ----------------------------------------------------------------------------
// SLVoice is the AudioBuffer_s->_internal
static inline void _opensl_destroyVoice(SLVoice *voice) {
if (voice->ringBuffer) {
FREE(voice->ringBuffer);
}
memset(voice, 0, sizeof(*voice));
FREE(voice);
}
static long opensl_destroySoundBuffer(const struct AudioContext_s *audio_context, INOUT AudioBuffer_s **soundbuf_struct) {
if (!*soundbuf_struct) {
return 0;
}
LOG("opensl_destroySoundBuffer ...");
EngineContext_s *ctx = (EngineContext_s *)(audio_context->_internal);
SLVoice *v = (SLVoice *)((*soundbuf_struct)->_internal);
SLVoice *vprev = NULL;
SLVoice *voice = ctx->voices;
while (voice) {
if (voice == v) {
if (vprev) {
vprev->next = voice->next;
} else {
ctx->voices = voice->next;
}
break;
}
vprev = voice;
voice = voice->next;
}
assert(voice && "voice should exist, or speaker, mockingboard, etc are not using this internal API correctly!");
// Do not actually destory the voice here since we could race with the buffer queue. purge these on complete sound
// system shutdown
voice->next = ctx->recycledVoices;
ctx->recycledVoices = voice;
memset(*soundbuf_struct, 0x0, sizeof(soundbuf_struct));
FREE(*soundbuf_struct);
return 0;
}
static long opensl_createSoundBuffer(const AudioContext_s *audio_context, INOUT AudioBuffer_s **soundbuf_struct) {
LOG("opensl_createSoundBuffer ...");
assert(*soundbuf_struct == NULL);
SLVoice *voice = NULL;
do {
EngineContext_s *ctx = (EngineContext_s *)(audio_context->_internal);
assert(ctx != NULL);
unsigned long bufferSize = opensles_audio_backend.systemSettings.stereoBufferSizeSamples * opensles_audio_backend.systemSettings.bytesPerSample * NUM_CHANNELS;
if (ctx->recycledVoices) {
LOG("Recycling previous SLVoice ...");
voice = ctx->recycledVoices;
ctx->recycledVoices = voice->next;
uint8_t *prevBuffer = voice->ringBuffer;
memset(voice, 0x0, sizeof(*voice));
voice->bufferSize = bufferSize;
voice->ringBuffer = prevBuffer;
} else {
LOG("Creating new SLVoice ...");
voice = CALLOC(1, sizeof(*voice));
if (voice == NULL) {
ERRLOG("OOPS, Out of memory!");
break;
}
voice->bufferSize = bufferSize;
// Allocate enough space for the temp buffer (including a maximum allowed overflow)
voice->ringBuffer = CALLOC(1, voice->bufferSize + ctx->submitSize/*max overflow*/);
if (voice->ringBuffer == NULL) {
ERRLOG("OOPS, Error allocating %lu bytes", (unsigned long)voice->bufferSize+ctx->submitSize);
break;
}
}
LOG("ideal stereo submission bufsize is %lu (bytes:%lu)", (unsigned long)android_stereoBufferSubmitSizeSamples, (unsigned long)ctx->submitSize);
voice->ctx = ctx;
if ((*soundbuf_struct = CALLOC(1, sizeof(AudioBuffer_s))) == NULL) {
ERRLOG("OOPS, Not enough memory");
break;
}
(*soundbuf_struct)->_internal = voice;
(*soundbuf_struct)->GetCurrentPosition = &SLGetPosition;
(*soundbuf_struct)->Lock = &SLLockBuffer;
(*soundbuf_struct)->Unlock = &SLUnlockBuffer;
(*soundbuf_struct)->GetStatus = &SLGetStatus;
// mockingboard-specific (SSI263) hacks
//(*soundbuf_struct)->UnlockStaticBuffer = &SLUnlockStaticBuffer;
//(*soundbuf_struct)->Replay = &SLReplay;
voice->next = ctx->voices;
ctx->voices = voice;
LOG("Successfully created SLVoice");
return 0;
} while(0);
if (*soundbuf_struct) {
opensl_destroySoundBuffer(audio_context, soundbuf_struct);
} else if (voice) {
_opensl_destroyVoice(voice);
}
return -1;
}
// ----------------------------------------------------------------------------
static long opensles_systemShutdown(AudioContext_s **audio_context) {
assert(*audio_context != NULL);
EngineContext_s *ctx = (EngineContext_s *)((*audio_context)->_internal);
assert(ctx != NULL);
// destroy buffer queue audio player object, and invalidate all associated interfaces
if (ctx->bqPlayerObject != NULL) {
(*(ctx->bqPlayerObject))->Destroy(ctx->bqPlayerObject);
ctx->bqPlayerObject = NULL;
ctx->bqPlayerPlay = NULL;
ctx->bqPlayerBufferQueue = NULL;
}
// destroy output mix object, and invalidate all associated interfaces
if (ctx->outputMixObject != NULL) {
(*(ctx->outputMixObject))->Destroy(ctx->outputMixObject);
ctx->outputMixObject = NULL;
}
// destroy engine object, and invalidate all associated interfaces
if (ctx->engineObject != NULL) {
(*(ctx->engineObject))->Destroy(ctx->engineObject);
ctx->engineObject = NULL;
ctx->engineEngine = NULL;
}
if (ctx->mixBuf) {
FREE(ctx->mixBuf);
}
assert(ctx->voices == NULL && "incorrect API usage");
SLVoice *voice = ctx->recycledVoices;
while (voice) {
SLVoice *vkill = voice;
voice = voice->next;
_opensl_destroyVoice(vkill);
}
memset(ctx, 0x0, sizeof(EngineContext_s));
FREE(ctx);
memset(*audio_context, 0x0, sizeof(AudioContext_s));
FREE(*audio_context);
return 0;
}
static long opensles_systemSetup(INOUT AudioContext_s **audio_context) {
assert(*audio_context == NULL);
EngineContext_s *ctx = NULL;
SLresult result = -1;
opensles_audio_backend.systemSettings.sampleRateHz = android_deviceSampleRateHz;
opensles_audio_backend.systemSettings.bytesPerSample = 2;
if (android_deviceSampleRateHz <= 22050/*sentinel in DevicePropertyCalculator.java*/) {
android_stereoBufferSubmitSizeSamples >>= 1; // value from Android/Java DevicePropertyCalculator.java seems to be pre-multiplied by channel size?
}
opensles_audio_backend.systemSettings.monoBufferSizeSamples = android_deviceSampleRateHz * audio_getLatency();
opensles_audio_backend.systemSettings.stereoBufferSizeSamples = android_deviceSampleRateHz * audio_getLatency();
if (android_stereoBufferSubmitSizeSamples<<2 > opensles_audio_backend.systemSettings.stereoBufferSizeSamples) {
opensles_audio_backend.systemSettings.stereoBufferSizeSamples = android_stereoBufferSubmitSizeSamples<<2;
LOG("Changing stereo buffer size to be %lu samples", (unsigned long)opensles_audio_backend.systemSettings.stereoBufferSizeSamples);
}
if (android_monoBufferSubmitSizeSamples<<2 > opensles_audio_backend.systemSettings.monoBufferSizeSamples) {
opensles_audio_backend.systemSettings.monoBufferSizeSamples = android_monoBufferSubmitSizeSamples<<2;
LOG("Changing mono buffer size to be %lu samples", (unsigned long)opensles_audio_backend.systemSettings.monoBufferSizeSamples);
}
#warning TODO FIXME ^^^^^ need a dynamic bufferSize calculation/calibration routine to determine optimal buffer size for device ... may also need a user-initiated calibration too
do {
//
// Engine creation ...
//
ctx = CALLOC(1, sizeof(EngineContext_s));
if (!ctx) {
result = -1;
break;
}
ctx->submitSize = android_stereoBufferSubmitSizeSamples * opensles_audio_backend.systemSettings.bytesPerSample * NUM_CHANNELS;
ctx->mixBuf = CALLOC(1, ctx->submitSize);
if (ctx->mixBuf == NULL) {
ERRLOG("OOPS, Error allocating %lu bytes", (unsigned long)ctx->submitSize);
break;
}
// create basic engine
result = slCreateEngine(&(ctx->engineObject), 0, NULL, /*engineMixIIDCount:*/0, /*engineMixIIDs:*/NULL, /*engineMixReqs:*/NULL);
if (result != SL_RESULT_SUCCESS) {
ERRLOG("Could not create OpenSLES Engine : %lu", (unsigned long)result);
break;
}
// realize the engine
result = (*(ctx->engineObject))->Realize(ctx->engineObject, /*asynchronous_realization:*/SL_BOOLEAN_FALSE);
if (result != SL_RESULT_SUCCESS) {
ERRLOG("Could not realize the OpenSLES Engine : %lu", (unsigned long)result);
break;
}
// get the actual engine interface
result = (*(ctx->engineObject))->GetInterface(ctx->engineObject, SL_IID_ENGINE, &(ctx->engineEngine));
if (result != SL_RESULT_SUCCESS) {
ERRLOG("Could not get the OpenSLES Engine : %lu", (unsigned long)result);
break;
}
//
// Output Mix ...
//
result = (*(ctx->engineEngine))->CreateOutputMix(ctx->engineEngine, &(ctx->outputMixObject), 0, NULL, NULL);
if (result != SL_RESULT_SUCCESS) {
ERRLOG("Could not create output mix : %lu", (unsigned long)result);
break;
}
// realize the output mix
result = (*(ctx->outputMixObject))->Realize(ctx->outputMixObject, SL_BOOLEAN_FALSE);
if (result != SL_RESULT_SUCCESS) {
ERRLOG("Could not realize the output mix : %lu", (unsigned long)result);
break;
}
// create soundcore API wrapper
if ((*audio_context = CALLOC(1, sizeof(AudioContext_s))) == NULL) {
result = -1;
ERRLOG("OOPS, Not enough memory");
break;
}
//
// OpenSLES buffer queue player setup
//
// configure audio source
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {
.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
.numBuffers = 2,
#warning FIXME TODO ... verify 2 numBuffers is best
};
SLDataFormat_PCM format_pcm = {
.formatType = SL_DATAFORMAT_PCM,
.numChannels = 2,
.samplesPerSec = opensles_audio_backend.systemSettings.sampleRateHz * 1000,
.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16,
.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16,
.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
.endianness = SL_BYTEORDER_LITTLEENDIAN,
};
SLDataSource audioSrc = {
.pLocator = &loc_bufq,
.pFormat = &format_pcm,
};
// configure audio sink
SLDataLocator_OutputMix loc_outmix = {
.locatorType = SL_DATALOCATOR_OUTPUTMIX,
.outputMix = ctx->outputMixObject,
};
SLDataSink audioSnk = {
.pLocator = &loc_outmix,
.pFormat = NULL,
};
// create audio player
#define _NUM_INTERFACES 3
const SLInterfaceID ids[_NUM_INTERFACES] = {
SL_IID_BUFFERQUEUE,
SL_IID_EFFECTSEND,
//SL_IID_MUTESOLO,
SL_IID_VOLUME,
};
const SLboolean req[_NUM_INTERFACES] = {
SL_BOOLEAN_TRUE,
SL_BOOLEAN_TRUE,
//numChannels == 1 ? SL_BOOLEAN_FALSE : SL_BOOLEAN_TRUE,
SL_BOOLEAN_TRUE,
};
result = (*(ctx->engineEngine))->CreateAudioPlayer(ctx->engineEngine, &(ctx->bqPlayerObject), &audioSrc, &audioSnk, _NUM_INTERFACES, ids, req);
if (result != SL_RESULT_SUCCESS) {
ERRLOG("OOPS, could not create the BufferQueue player object : %lu", (unsigned long)result);
break;
}
// realize the player
result = (*(ctx->bqPlayerObject))->Realize(ctx->bqPlayerObject, /*asynchronous_realization:*/SL_BOOLEAN_FALSE);
if (result != SL_RESULT_SUCCESS) {
ERRLOG("OOPS, could not realize the BufferQueue player object : %lu", (unsigned long)result);
break;
}
// get the play interface
result = (*(ctx->bqPlayerObject))->GetInterface(ctx->bqPlayerObject, SL_IID_PLAY, &(ctx->bqPlayerPlay));
if (result != SL_RESULT_SUCCESS) {
ERRLOG("OOPS, could not get the play interface : %lu", (unsigned long)result);
break;
}
// get the buffer queue interface
result = (*(ctx->bqPlayerObject))->GetInterface(ctx->bqPlayerObject, SL_IID_BUFFERQUEUE, &(ctx->bqPlayerBufferQueue));
if (result != SL_RESULT_SUCCESS) {
ERRLOG("OOPS, could not get the BufferQueue play interface : %lu", (unsigned long)result);
break;
}
// register callback on the buffer queue
result = (*(ctx->bqPlayerBufferQueue))->RegisterCallback(ctx->bqPlayerBufferQueue, bqPlayerCallback, ctx);
if (result != SL_RESULT_SUCCESS) {
ERRLOG("OOPS, could not register BufferQueue callback : %lu", (unsigned long)result);
break;
}
(*audio_context)->_internal = ctx;
(*audio_context)->CreateSoundBuffer = &opensl_createSoundBuffer;
(*audio_context)->DestroySoundBuffer = &opensl_destroySoundBuffer;
LOG("Successfully created OpenSLES engine and buffer queue");
} while (0);
if (result != SL_RESULT_SUCCESS) {
if (ctx) {
AudioContext_s *ctxPtr = CALLOC(1, sizeof(AudioContext_s));
ctxPtr->_internal = ctx;
opensles_systemShutdown(&ctxPtr);
}
assert(*audio_context == NULL);
LOG("OpenSLES sound output disabled");
}
return result;
}
// pause all audio
static long opensles_systemPause(AudioContext_s *audio_context) {
LOG("OpenSLES pausing play");
EngineContext_s *ctx = (EngineContext_s *)(audio_context->_internal);
SLresult result = (*(ctx->bqPlayerPlay))->SetPlayState(ctx->bqPlayerPlay, SL_PLAYSTATE_PAUSED);
return 0;
}
static long opensles_systemResume(AudioContext_s *audio_context) {
LOG("OpenSLES resuming play");
SLuint32 state = 0;
EngineContext_s *ctx = (EngineContext_s *)(audio_context->_internal);
SLresult result = (*(ctx->bqPlayerPlay))->GetPlayState(ctx->bqPlayerPlay, &state);
do {
if (result != SL_RESULT_SUCCESS) {
ERRLOG("OOPS, could not get source state when attempting to resume : %lu", (unsigned long)result);
break;
}
if (state != SL_PLAYSTATE_PLAYING) {
ERRLOG("WARNING: possible audio lifecycle mismatch ... continuing anyway");
}
if (state == SL_PLAYSTATE_PAUSED) {
// Balanced resume OK here
SLresult result = (*(ctx->bqPlayerPlay))->SetPlayState(ctx->bqPlayerPlay, SL_PLAYSTATE_PLAYING);
} else if (state == SL_PLAYSTATE_STOPPED) {
// Do not resume for stopped state, let this get forced from CPU/speaker thread otherwise we starve. (The
// stopped state happens if user dynamically changed buffer parameters in menu settings which triggered an
// OpenSLES destroy/re-initialization ... e.g. audio_setLatency() was invoked)
}
} while (0);
return result;
}
static void _init_opensl(void) {
LOG("Initializing OpenSLES sound system");
assert(audio_backend == NULL && "there can only be one!");
opensles_audio_backend.setup = &opensles_systemSetup;
opensles_audio_backend.shutdown = &opensles_systemShutdown;
opensles_audio_backend.pause = &opensles_systemPause;
opensles_audio_backend.resume = &opensles_systemResume;
audio_backend = &opensles_audio_backend;
}
static __attribute__((constructor)) void __init_opensl(void) {
emulator_registerStartupCallback(CTOR_PRIORITY_EARLY, &_init_opensl);
}

View File

@ -1,137 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
/*
* Apple //e core sound system support. Source inspired/derived from AppleWin.
*
* 2015/10/01 AUDIO LIFECYCLE WARNING : CPU thread owns all audio, otherwise bad things may happen in system sound layer
* (OpenAL/OpenSLES/ALSA/etc)
*/
#include "common.h"
#define MAX_SOUND_DEVICES 100
static AudioContext_s *audioContext = NULL;
bool audio_isAvailable = false;
float audio_latencySecs = 0.25f;
AudioBackend_s *audio_backend = NULL;
//-----------------------------------------------------------------------------
long audio_createSoundBuffer(INOUT AudioBuffer_s **audioBuffer) {
// CPU thread owns audio lifecycle (see note above)
assert(pthread_self() == cpu_thread_id);
if (!audio_isAvailable) {
*audioBuffer = NULL;
return -1;
}
if (*audioBuffer) {
audio_destroySoundBuffer(audioBuffer);
}
long err = 0;
do {
if (!audioContext) {
ERRLOG("Cannot create sound buffer, no context");
err = -1;
break;
}
err = audioContext->CreateSoundBuffer(audioContext, audioBuffer);
if (err) {
break;
}
} while (0);
return err;
}
void audio_destroySoundBuffer(INOUT AudioBuffer_s **audioBuffer) {
// CPU thread owns audio lifecycle (see note above)
assert(pthread_self() == cpu_thread_id);
if (audioContext) {
audioContext->DestroySoundBuffer(audioContext, audioBuffer);
}
}
bool audio_init(void) {
// CPU thread owns audio lifecycle (see note above)
assert(pthread_self() == cpu_thread_id);
if (audio_isAvailable) {
return true;
}
do {
if (!audio_backend) {
LOG("No backend audio available, cannot initialize soundcore");
break;
}
if (audioContext) {
audio_backend->shutdown(&audioContext);
}
long err = audio_backend->setup((AudioContext_s**)&audioContext);
if (err) {
LOG("Failed to create an audio context!");
break;
}
audio_isAvailable = true;
} while (0);
return audio_isAvailable;
}
void audio_shutdown(void) {
// CPU thread owns audio lifecycle (see note above)
assert(pthread_self() == cpu_thread_id);
if (!audio_isAvailable) {
return;
}
audio_backend->shutdown(&audioContext);
audio_isAvailable = false;
}
void audio_pause(void) {
// CPU thread owns audio lifecycle (see note above)
// Deadlock on Kindle Fire 1st Gen if audio_pause() and audio_resume() happen off CPU thread ...
#ifdef __APPLE__
# warning FIXME TODO : this assert is firing on iOS port ... but the assert is valid ... fix soon
#else
assert(pthread_self() == cpu_thread_id);
#endif
if (!audio_isAvailable) {
return;
}
audio_backend->pause(audioContext);
}
void audio_resume(void) {
// CPU thread owns audio lifecycle (see note above)
assert(pthread_self() == cpu_thread_id);
if (!audio_isAvailable) {
return;
}
audio_backend->resume(audioContext);
}
void audio_setLatency(float latencySecs) {
audio_latencySecs = latencySecs;
}
float audio_getLatency(void) {
return audio_latencySecs;
}

View File

@ -1,152 +0,0 @@
/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
/*
* Apple //e core sound system support. Source inspired/derived from AppleWin.
*
*/
#ifndef _SOUNDCORE_H_
#define _SOUNDCORE_H_
#define AUDIO_STATUS_PLAYING 0x00000001
#define AUDIO_STATUS_NOTPLAYING 0x08000000
// AppleWin-sourced default error increment and max adjustment values ...
#define SOUNDCORE_ERROR_INC 20
#define SOUNDCORE_ERROR_MAX 200
// Note to future self:
//
// Although output for the speaker could be MONO (and was at one point in time) ... we optimize the OpenSLES backend on
// Android to have just one buffer queue callback, where we need to mix both mockingboard and speaker samples.
//
// For now, just make everything use stereo for simplicity (including OpenAL backend).
#define NUM_CHANNELS 2
typedef struct AudioBuffer_s {
bool bActive; // Mockingboard ... refactor?
bool bMute; // Mockingboard ... refactor?
long nVolume; // Mockingboard ... refactor?
PRIVATE void *_internal;
// Get current number of queued bytes
long (*GetCurrentPosition)(struct AudioBuffer_s *_this, OUTPARM unsigned long *bytes_queued);
// This method obtains a valid write pointer to the sound buffer's audio data
long (*Lock)(struct AudioBuffer_s *_this, unsigned long write_bytes, INOUT int16_t **audio_ptr, OUTPARM unsigned long *audio_bytes);
// This method releases a locked sound buffer.
long (*Unlock)(struct AudioBuffer_s *_this, unsigned long audio_bytes);
// Get status (playing or not)
long (*GetStatus)(struct AudioBuffer_s *_this, OUTPARM unsigned long *status);
// Mockingboard-specific buffer replay
//long (*UnlockStaticBuffer)(struct AudioBuffer_s *_this, unsigned long audio_bytes);
//long (*Replay)(struct AudioBuffer_s *_this);
} AudioBuffer_s;
/*
* Creates a sound buffer object.
*/
long audio_createSoundBuffer(INOUT AudioBuffer_s **audioBuffer);
/*
* Destroy and nullify sound buffer object.
*/
void audio_destroySoundBuffer(INOUT AudioBuffer_s **pVoice);
/*
* Prepare the audio subsystem, including the backend renderer.
*/
bool audio_init(void);
/*
* Shutdown the audio subsystem and backend renderer.
*/
void audio_shutdown(void);
/*
* Pause the audio subsystem.
*/
void audio_pause(void);
/*
* Resume the audio subsystem.
*/
void audio_resume(void);
/*
* Set audio buffer latency
*/
void audio_setLatency(float latencySecs);
/*
* Get audio buffer latency
*/
float audio_getLatency(void);
/*
* Is the audio subsystem available?
*/
extern READONLY bool audio_isAvailable;
typedef struct AudioSettings_s {
/*
* Native device sample rate
*/
READONLY unsigned long sampleRateHz;
/*
* Native device bytes-per-sample (currently assuming 16bit/2byte samples)
*/
READONLY unsigned long bytesPerSample;
/*
* Native mono min/ideal buffer size in samples
*/
READONLY unsigned long monoBufferSizeSamples;
/*
* Native stereo min/ideal buffer size in samples
*/
READONLY unsigned long stereoBufferSizeSamples;
} AudioSettings_s;
// ----------------------------------------------------------------------------
// Private audio backend APIs
typedef struct AudioContext_s {
PRIVATE void *_internal;
PRIVATE long (*CreateSoundBuffer)(const struct AudioContext_s *sound_system, INOUT AudioBuffer_s **buffer);
PRIVATE long (*DestroySoundBuffer)(const struct AudioContext_s *sound_system, INOUT AudioBuffer_s **buffer);
} AudioContext_s;
typedef struct AudioBackend_s {
AudioSettings_s systemSettings;
// basic backend functionality controlled by soundcore
PRIVATE long (*setup)(INOUT AudioContext_s **audio_context);
PRIVATE long (*shutdown)(INOUT AudioContext_s **audio_context);
PRIVATE long (*pause)(AudioContext_s *audio_context);
PRIVATE long (*resume)(AudioContext_s *audio_context);
} AudioBackend_s;
// Audio backend registered at CTOR time
extern AudioBackend_s *audio_backend;
#endif /* whole file */

View File

@ -144,7 +144,7 @@ bool BIOS::runUntilDone()
done:
// Undo whatever damage we've done to the screen
g_display->redraw();
g_display->blit({0, 0, 279, 191});
g_display->blit({0, 0, 191, 279});
// return true if any persistent setting changed that we want to store in eeprom
return volumeDidChange;

View File

@ -1 +0,0 @@
../apple/mockingboard.cpp

View File

@ -1 +0,0 @@
../apple/mockingboard.h

View File

@ -137,7 +137,7 @@ void setup()
// Debugging: insert a disk on startup...
// ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/UTIL/mock2dem.dsk", false);
((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/JORJ/disk_s6d1.dsk", false);
// ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/JORJ/disk_s6d1.dsk", false);
// ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/GAMES/ALIBABA.DSK", false);
pinMode(56, OUTPUT);