blob: 8f069ca8f1c63a72012346f21e1165ccc7f91865 [file] [log] [blame] [edit]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <string.h>
#include "log.h"
#include "parameters.h"
#include "thread.h"
#include "async_handle.h"
#include "async_alarm.h"
#include "async_event.h"
#include "async_wait.h"
#include "program.h"
#include "tune.h"
#include "notes.h"
static int tuneInitialized = 0;
static AsyncHandle tuneDeviceCloseTimer = NULL;
static int openErrorLevel = LOG_ERR;
static const NoteMethods *noteMethods = NULL;
static NoteDevice *noteDevice = NULL;
static int
flushNoteDevice (void) {
if (!noteDevice) return 1;
return noteMethods->flush(noteDevice);
}
static void
closeTuneDevice (void) {
if (tuneDeviceCloseTimer) {
asyncCancelRequest(tuneDeviceCloseTimer);
tuneDeviceCloseTimer = NULL;
}
if (noteDevice) {
noteMethods->destruct(noteDevice);
noteDevice = NULL;
}
}
ASYNC_ALARM_CALLBACK(handleTuneDeviceCloseTimeout) {
if (tuneDeviceCloseTimer) {
asyncDiscardHandle(tuneDeviceCloseTimer);
tuneDeviceCloseTimer = NULL;
}
closeTuneDevice();
}
static int
openTuneDevice (void) {
const int timeout = TUNE_DEVICE_CLOSE_DELAY;
if (noteDevice) {
asyncResetAlarmIn(tuneDeviceCloseTimer, timeout);
return 1;
}
if (noteMethods) {
if ((noteDevice = noteMethods->construct(openErrorLevel)) != NULL) {
asyncNewRelativeAlarm(&tuneDeviceCloseTimer, timeout, handleTuneDeviceCloseTimeout, NULL);
return 1;
}
}
return 0;
}
static const NoteElement *currentlyPlayingNotes = NULL;
static const ToneElement *currentlyPlayingTones = NULL;
typedef unsigned char TuneSynchronizationMonitor;
typedef enum {
TUNE_REQ_SET_DEVICE,
TUNE_REQ_PLAY_NOTES,
TUNE_REQ_PLAY_TONES,
TUNE_REQ_WAIT,
TUNE_REQ_SYNCHRONIZE
} TuneRequestType;
typedef struct {
TuneRequestType type;
union {
struct {
const NoteMethods *methods;
} setDevice;
struct {
const NoteElement *tune;
} playNotes;
struct {
const ToneElement *tune;
} playTones;
struct {
int time;
} wait;
struct {
TuneSynchronizationMonitor *monitor;
} synchronize;
} parameters;
} TuneRequest;
static void
handleTuneRequest_setDevice (const NoteMethods *methods) {
if (methods != noteMethods) {
closeTuneDevice();
noteMethods = methods;
}
}
static void
handleTuneRequest_playNotes (const NoteElement *tune) {
while (tune->duration) {
if (!openTuneDevice()) return;
if (!noteMethods->note(noteDevice, tune->duration, tune->note)) return;
tune += 1;
}
flushNoteDevice();
}
static void
handleTuneRequest_playTones (const ToneElement *tune) {
while (tune->duration) {
if (!openTuneDevice()) return;
if (!noteMethods->tone(noteDevice, tune->duration, tune->frequency)) return;
tune += 1;
}
flushNoteDevice();
}
static void
handleTuneRequest_wait (int time) {
asyncWait(time);
}
static void
handleTuneRequest_synchronize (TuneSynchronizationMonitor *monitor) {
*monitor = 1;
}
static void
handleTuneRequest (TuneRequest *req) {
if (req) {
switch (req->type) {
case TUNE_REQ_SET_DEVICE:
handleTuneRequest_setDevice(req->parameters.setDevice.methods);
break;
case TUNE_REQ_PLAY_NOTES: {
const NoteElement *tune = req->parameters.playNotes.tune;
currentlyPlayingNotes = tune;
handleTuneRequest_playNotes(tune);
currentlyPlayingNotes = NULL;
break;
}
case TUNE_REQ_PLAY_TONES: {
const ToneElement *tune = req->parameters.playTones.tune;
currentlyPlayingTones = tune;
handleTuneRequest_playTones(tune);
currentlyPlayingTones = NULL;
break;
}
case TUNE_REQ_WAIT:
handleTuneRequest_wait(req->parameters.wait.time);
break;
case TUNE_REQ_SYNCHRONIZE:
handleTuneRequest_synchronize(req->parameters.synchronize.monitor);
break;
}
free(req);
} else {
closeTuneDevice();
}
}
#ifdef GOT_PTHREADS
typedef enum {
TUNE_THREAD_NONE,
TUNE_THREAD_STARTING,
TUNE_THREAD_FAILED,
TUNE_THREAD_RUNNING,
TUNE_THREAD_STOPPING,
TUNE_THREAD_STOPPED
} TuneThreadState;
static TuneThreadState tuneThreadState = TUNE_THREAD_NONE;
static pthread_t tuneThreadIdentifier;
static AsyncEvent *tuneRequestEvent = NULL;
static AsyncEvent *tuneMessageEvent = NULL;
static void
setTuneThreadState (TuneThreadState newState) {
TuneThreadState oldState = tuneThreadState;
logMessage(LOG_DEBUG, "tune thread state change: %d -> %d", oldState, newState);
tuneThreadState = newState;
}
ASYNC_CONDITION_TESTER(testTuneThreadStarted) {
return tuneThreadState != TUNE_THREAD_STARTING;
}
ASYNC_CONDITION_TESTER(testTuneThreadStopping) {
return tuneThreadState == TUNE_THREAD_STOPPING;
}
ASYNC_CONDITION_TESTER(testTuneThreadStopped) {
return tuneThreadState == TUNE_THREAD_STOPPED;
}
typedef enum {
TUNE_MSG_SET_STATE
} TuneMessageType;
typedef struct {
TuneMessageType type;
union {
struct {
TuneThreadState state;
} setState;
} parameters;
} TuneMessage;
static void
handleTuneMessage (TuneMessage *msg) {
switch (msg->type) {
case TUNE_MSG_SET_STATE:
setTuneThreadState(msg->parameters.setState.state);
break;
}
free(msg);
}
ASYNC_EVENT_CALLBACK(handleTuneMessageEvent) {
TuneMessage *msg = parameters->signalData;
if (msg) handleTuneMessage(msg);
}
static int
sendTuneMessage (TuneMessage *msg) {
return asyncSignalEvent(tuneMessageEvent, msg);
}
static TuneMessage *
newTuneMessage (TuneMessageType type) {
TuneMessage *msg;
if ((msg = malloc(sizeof(*msg)))) {
memset(msg, 0, sizeof(*msg));
msg->type = type;
return msg;
} else {
logMallocError();
}
return NULL;
}
static void
sendTuneThreadState (TuneThreadState state) {
TuneMessage *msg;
if ((msg = newTuneMessage(TUNE_MSG_SET_STATE))) {
msg->parameters.setState.state = state;
if (!sendTuneMessage(msg)) free(msg);
}
}
static void
finishTuneRequest_stop (void) {
setTuneThreadState(TUNE_THREAD_STOPPING);
}
static void
finishTuneRequest_synchronize (void) {
sendTuneMessage(NULL);
}
ASYNC_EVENT_CALLBACK(handleTuneRequestEvent) {
TuneRequest *req = parameters->signalData;
void (*finish) (void) = NULL;
if (req) {
switch (req->type) {
case TUNE_REQ_SYNCHRONIZE:
finish = finishTuneRequest_synchronize;
break;
default:
break;
}
} else {
finish = finishTuneRequest_stop;
}
handleTuneRequest(req);
if (finish) finish();
}
THREAD_FUNCTION(runTuneThread) {
if ((tuneRequestEvent = asyncNewEvent(handleTuneRequestEvent, NULL))) {
sendTuneThreadState(TUNE_THREAD_RUNNING);
asyncWaitFor(testTuneThreadStopping, NULL);
asyncDiscardEvent(tuneRequestEvent);
tuneRequestEvent = NULL;
}
sendTuneThreadState(TUNE_THREAD_STOPPED);
return NULL;
}
static int
startTuneThread (void) {
if (tuneThreadState == TUNE_THREAD_NONE) {
setTuneThreadState(TUNE_THREAD_STARTING);
if ((tuneMessageEvent = asyncNewEvent(handleTuneMessageEvent, NULL))) {
int creationError = createThread("tune-thread", &tuneThreadIdentifier,
NULL, runTuneThread, NULL);
if (!creationError) {
asyncWaitFor(testTuneThreadStarted, NULL);
if (tuneThreadState == TUNE_THREAD_RUNNING) return 1;
} else {
logActionError(creationError, "tune thread creation");
setTuneThreadState(TUNE_THREAD_FAILED);
}
asyncDiscardEvent(tuneMessageEvent);
tuneMessageEvent = NULL;
}
}
return tuneThreadState == TUNE_THREAD_RUNNING;
}
#endif /* GOT_PTHREADS */
static int
sendTuneRequest (TuneRequest *req) {
#ifdef GOT_PTHREADS
if (startTuneThread()) {
return asyncSignalEvent(tuneRequestEvent, req);
}
#endif /* GOT_PTHREADS */
handleTuneRequest(req);
return 1;
}
static void
exitTunes (void *data) {
sendTuneRequest(NULL);
#ifdef GOT_PTHREADS
if (tuneThreadState >= TUNE_THREAD_RUNNING) {
asyncWaitFor(testTuneThreadStopped, NULL);
}
tuneThreadState = TUNE_THREAD_NONE;
#endif /* GOT_PTHREADS */
tuneInitialized = 0;
}
static TuneRequest *
newTuneRequest (TuneRequestType type) {
TuneRequest *req;
if (!tuneInitialized) {
tuneInitialized = 1;
onProgramExit("tunes", exitTunes, NULL);
}
if ((req = malloc(sizeof(*req)))) {
memset(req, 0, sizeof(*req));
req->type = type;
return req;
} else {
logMallocError();
}
return NULL;
}
int
tuneSetDevice (TuneDevice device) {
const NoteMethods *methods;
switch (device) {
default:
return 0;
#ifdef HAVE_BEEP_SUPPORT
case tdBeeper:
methods = &beepNoteMethods;
break;
#endif /* HAVE_BEEP_SUPPORT */
#ifdef HAVE_PCM_SUPPORT
case tdPcm:
methods = &pcmNoteMethods;
break;
#endif /* HAVE_PCM_SUPPORT */
#ifdef HAVE_MIDI_SUPPORT
case tdMidi:
methods = &midiNoteMethods;
break;
#endif /* HAVE_MIDI_SUPPORT */
#ifdef HAVE_FM_SUPPORT
case tdFm:
methods = &fmNoteMethods;
break;
#endif /* HAVE_FM_SUPPORT */
}
{
TuneRequest *req;
if ((req = newTuneRequest(TUNE_REQ_SET_DEVICE))) {
req->parameters.setDevice.methods = methods;
if (!sendTuneRequest(req)) free(req);
}
}
return 1;
}
void
tunePlayNotes (const NoteElement *tune) {
if (tune != currentlyPlayingNotes) {
TuneRequest *req;
if ((req = newTuneRequest(TUNE_REQ_PLAY_NOTES))) {
req->parameters.playNotes.tune = tune;
if (!sendTuneRequest(req)) free(req);
}
}
}
void
tunePlayTones (const ToneElement *tune) {
if (tune != currentlyPlayingTones) {
TuneRequest *req;
if ((req = newTuneRequest(TUNE_REQ_PLAY_TONES))) {
req->parameters.playTones.tune = tune;
if (!sendTuneRequest(req)) free(req);
}
}
}
void
tuneWait (int time) {
TuneRequest *req;
if ((req = newTuneRequest(TUNE_REQ_WAIT))) {
req->parameters.wait.time = time;
if (!sendTuneRequest(req)) free(req);
}
}
ASYNC_CONDITION_TESTER(testTuneSynchronizationMonitor) {
TuneSynchronizationMonitor *monitor = data;
return !!*monitor;
}
void
tuneSynchronize (void) {
TuneRequest *req;
if ((req = newTuneRequest(TUNE_REQ_SYNCHRONIZE))) {
TuneSynchronizationMonitor monitor = 0;
req->parameters.synchronize.monitor = &monitor;
if (sendTuneRequest(req)) {
asyncWaitFor(testTuneSynchronizationMonitor, &monitor);
} else {
free(req);
}
}
}
void
suppressTuneDeviceOpenErrors (void) {
openErrorLevel = LOG_DEBUG;
}