home *** CD-ROM | disk | FTP | other *** search
- /* File: sound.c
- * Created: 20-10-95
- * Updated: 17-05-96
- * Version: 1.3
- * Project: Clicker
- * Owner: Jeroen Vermeulen
- * Requirements: KickStart V39+
- * Legal: PD
- * Status: Release
- */
-
- #include <math.h>
-
- #include <proto/exec.h>
- #include <exec/devices.h>
- #include <exec/memory.h>
- #include <devices/audio.h>
- #include <proto/alib.h>
-
- #include "sample.h"
- #include "main.h"
- #include "sound.h"
- #include "prefs.h"
-
-
- static STRPTR
- AllocFailPubMem = "Not enough PUBLIC memory!\n",
- OpenFailAudioDev = "Can't open audio.device\n";
-
-
- /* Allocation priority array for sound channels. I don't much care which one
- * we get; we only need one.
- */
- static unsigned char SoundChannels[] = { 1,2,4,8 };
-
-
- /* CreateSample():
- * Open audio device and sample. An IOAudio structure is returned unless either
- * an error occurs or the error string was already non-NULL before the call.
- * ClickPrefs must also be properly initialized before KeyClick() can be called.
- */
- struct IOAudio *CreateSample(STRPTR *const error)
- {
- struct IOAudio *soundrequest = NULL;
- struct IORequest *plainrequest;
- struct MsgPort *Reply;
-
- if ((Reply = CreateMsgPort()))
- {
- plainrequest = CreateIORequest(Reply,sizeof(struct IOAudio));
- soundrequest = (struct IOAudio *)plainrequest;
-
- if (soundrequest)
- {
- if (OpenDevice("audio.device",0,plainrequest,0) == 0)
- {
- /* We've succeeded in opening the audio.device, which is all that
- * matters right now. We'll try to allocate a sound channel, but
- * KeyClick() will know what to do if we fail.
- */
- plainrequest->io_Message.mn_Node.ln_Pri = -128; /* Never steal channels */
- plainrequest->io_Command = ADCMD_ALLOCATE;
- plainrequest->io_Flags = ADIOF_NOWAIT;
- soundrequest->ioa_Data = SoundChannels;
- soundrequest->ioa_Length = sizeof(SoundChannels);
- BeginIO(plainrequest);
- WaitIO(plainrequest);
-
- /* Now set up for first click! To think I forgot this once and have
- * been after the bug for weeks...
- * The lesson is: Never assume you're not stupid enough for something.
- * (I know what you're thinking: "I may be stupid but I'm not stupid
- * enough to make _that_ assumption")
- */
- plainrequest->io_Command = CMD_WRITE;
- plainrequest->io_Flags = ADIOF_PERVOL;
- soundrequest->ioa_Data = (signed char *)Sample;
- soundrequest->ioa_Length = SAMPLELENGTH;
- }
- else *error = OpenFailAudioDev;
- }
- else *error = AllocFailPubMem;
- }
- else *error = AllocFailMsgPort;
-
- if (*error)
- {
- DeleteSample(soundrequest);
- soundrequest = NULL;
- }
-
- return soundrequest;
- }
-
-
- /* Destroy sample and free all resources allocated with it. This function is
- * overly robust so it can be used from within CreateSample() in case of an
- * error.
- */
- void DeleteSample(struct IOAudio *const soundrequest)
- {
- if (soundrequest)
- {
- struct IORequest *const plainrequest = &soundrequest->ioa_Request;
- struct MsgPort *const ReplyPort = plainrequest->io_Message.mn_ReplyPort;
- const struct Device *const io_Device = plainrequest->io_Device;
- /* --- */
- if (io_Device && ((long)io_Device != -1)) CloseDevice(plainrequest);
- DeleteIORequest(plainrequest);
- if (ReplyPort) DeleteMsgPort(ReplyPort);
- }
- }
-
-
- /* KeyClick():
- * Make key-click noise. The soundrequest pointer is assumed to be valid and
- * non-NULL, and point at a properly initialized IOAudio structure.
- * The IOAudio structure must be set up for a WRITE request; this is its
- * default state to which it is also restored at the end of the function.
- * This function accesses ClickPrefs; make sure it is set up properly (and set
- * its "newsettings" flag for the initial call).
- *
- * If the previous click failed due to the channel being stolen, we attempt to
- * allocate a new channel.
- */
- void KeyClick(struct IOAudio *const soundrequest)
- {
- struct IORequest *const plainrequest = &soundrequest->ioa_Request;
- /* --- */
-
- /* Allocate channel if necessary */
- if (plainrequest->io_Error) /* Channel stolen or allocation failed */
- {
- plainrequest->io_Error = 0;
- plainrequest->io_Flags = ADIOF_NOWAIT;
- plainrequest->io_Command = ADCMD_ALLOCATE;
- soundrequest->ioa_Data = SoundChannels;
- soundrequest->ioa_Length = sizeof(SoundChannels);
- soundrequest->ioa_AllocKey = 0;
-
- BeginIO(plainrequest);
-
- /* This line is in a strange place, admittedly. I put it here just so I
- * might catch a little low-level parallellism on the off-chance.
- * If I have to do my audio I/O asynchronously I expect to be able to
- * exploit some of that somewhere. This is my chance!
- */
- ClickPrefs.newsettings = TRUE; /* Reload sound settings before click */
-
- WaitIO(plainrequest);
- if (plainrequest->io_Error) return;
-
- /* Set up for click */
- plainrequest->io_Command = CMD_WRITE;
- soundrequest->ioa_Data = (signed char *)Sample;
- soundrequest->ioa_Length = SAMPLELENGTH;
- }
-
- /* Prefs */
- if (ClickPrefs.newsettings)
- {
- ClickPrefs.newsettings = FALSE;
- soundrequest->ioa_Period = ClickPrefs.period;
- soundrequest->ioa_Volume = ClickPrefs.volume;
- soundrequest->ioa_Cycles = ClickPrefs.cycles;
- plainrequest->io_Flags = ADIOF_PERVOL;
- }
-
- /* Click */
- BeginIO(plainrequest);
- WaitIO(plainrequest);
-
- /* Sound request is ready for a new write command on exit */
- }
-
-
- /* SliderToHertz():
- * Converts a prefs window slider position (between -5*12 and 4*12) to a
- * frequency in Hertz, based on a twelve-tone octave centered at the 440 Hz A.
- */
- LONG SliderToHertz(const struct Gadget *const dum, const WORD sliderpos)
- {
- /* Frequency is 440 * 2^(n/12). Pity we can't use left-shift here!
- */
- return (LONG)(440.0 * pow(2.0,sliderpos/12.0));
- }
-
-
- /* HertzToPeriod():
- * Converts human-readable pitch in Hertz to period length suitable for use by
- * audio.device. As a rule, HertzToPeriod(SliderToHertz(S)) is equivalent to
- * SliderToPeriod(S).
- */
- UWORD HertzToPeriod(const LONG Hertz)
- {
- /* Period should be computed from frequency f as 10^9 / (279.365 * s * f),
- * where s is the sample length. With proper constant folding, this should
- * compile to (C/Hertz) where C is a constant.
- */
- return (UWORD)((1000000000.0/(279.365*SAMPLELENGTH))/Hertz);
- }
-
-
- /* PeriodToHertz():
- * Converts audio.device period length to human-readable pitch in Hertz. This
- * is the inverse of HertzToPeriod().
- */
- LONG PeriodToHertz(const UWORD period)
- {
- /* Pitch in Hertz is computed from period f as 279.365 * s * p / 10^9, where s
- * is sample length.
- */
- return (LONG)((279.365 * SAMPLELENGTH / 1000000000.0) * period);
- }
-
-
- /* SliderToPeriod():
- * Converts a prefs window slider position (between 0 and 9*12) to a period
- * length (in units of 279.365 nanoseconds) suitable for use by audio.device.
- */
- UWORD SliderToPeriod(const WORD sliderpos)
- {
- /* For a frequency f, the period length is 10^9 / (279.365 * s * f). Here s
- * is the length of the waveform sample (SAMPLELENGTH).
- * So we need to compute 10^9 / (279.365 * s * (440 * 2^(n/12)))
- * == 2^(-n/12) * 10^9 / (279.365 * 440 * s)
- * == 2^(-n/12) * 10^9 / (122920.6 * s)
- */
- return (UWORD)(pow(2.0,-(double)sliderpos/12.0) *
- (10000000.0 / (1229.206 * SAMPLELENGTH)));
- }
-
-
- /* PeriodToSlider():
- * Inverse of SliderToPeriod(). Takes a period length as an argument and
- * computes the appropriate slider position (between -5*12 and 4*12). This
- * function can afford to be slow because it's only ever called when the prefs
- * window pops up.
- */
- WORD PeriodToSlider(const UWORD period)
- {
- /* Since the period number p can be computed from slider position n by
- * p = 10^9 / (279.365 * 440 * s * 2^(n/12)) <==>
- * 2^(n/12) = 10^9 / (279.365 * 440 * s * p) <==>
- * n/12 = 2log(10^9 / (279.365 * 440 * s * p)) <==>
- * n = 12 * 2log(10^9 / (279.365 * 440 * s * p)) ==
- * 12 * 2log(10^9 / (122920.6 * s * p)) ==
- * 12 * ln(10^9 / (122920.6 * s * p)) / ln(2)
- */
- return (WORD)(12.0 * log(
- 1000000000.0 / (122920.6*SAMPLELENGTH*period)
- )/log(2.0));
- }
-
-