mirror of
https://github.com/JorjBauer/aiie.git
synced 2024-12-28 05:29:51 +00:00
remove/branch audio; remove debugging initial disk insertion; fix order of params in blit
This commit is contained in:
parent
dd523aac98
commit
89a6450a91
2
Makefile
2
Makefile
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
1666
audio/AY8910.c
1666
audio/AY8910.c
File diff suppressed because it is too large
Load Diff
129
audio/AY8910.h
129
audio/AY8910.h
@ -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
|
19620
audio/SSI263Phonemes.h
19620
audio/SSI263Phonemes.h
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
@ -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 */
|
3382
audio/mockingboard.c
3382
audio/mockingboard.c
File diff suppressed because it is too large
Load Diff
@ -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
|
@ -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
|
@ -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
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 */
|
@ -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;
|
||||
|
@ -1 +0,0 @@
|
||||
../apple/mockingboard.cpp
|
@ -1 +0,0 @@
|
||||
../apple/mockingboard.h
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user