/* * 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-2019 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 . */ #include "prologue.h" #include #include "log.h" #include "parameters.h" #include "thread.h" #include "async_event.h" #include "async_alarm.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; }