1 /*
2  * Copyright (C) 2002 - David W. Durham
3  *
4  * This file is part of ReZound, an audio editing application.
5  *
6  * ReZound is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published
8  * by the Free Software Foundation; either version 2 of the License,
9  * or (at your option) any later version.
10  *
11  * ReZound is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
19  */
20 
21 #include "CPortAudioSoundPlayer.h"
22 
23 #ifdef ENABLE_PORTAUDIO
24 
25 #include <stdio.h>
26 #include <string.h>
27 
28 #include <stdexcept>
29 
30 #include "settings.h"
31 
32 
33 
CPortAudioSoundPlayer()34 CPortAudioSoundPlayer::CPortAudioSoundPlayer() :
35 	ASoundPlayer(),
36 
37 	initialized(false),
38 	stream(NULL),
39 	supportsFullDuplex(true)
40 {
41 	PaError err=Pa_Initialize();
42 	if(err!=paNoError)
43 		throw runtime_error(string(__func__)+" -- error initializing PortAudio -- "+Pa_GetErrorText(err));
44 }
45 
~CPortAudioSoundPlayer()46 CPortAudioSoundPlayer::~CPortAudioSoundPlayer()
47 {
48 	deinitialize();
49 	Pa_Terminate();
50 }
51 
isInitialized() const52 bool CPortAudioSoundPlayer::isInitialized() const
53 {
54 	return initialized;
55 }
56 
initialize()57 void CPortAudioSoundPlayer::initialize()
58 {
59 	if(!initialized)
60 	{
61 		PaError err;
62 
63 		PaSampleFormat sampleFormat;
64 #if defined(SAMPLE_TYPE_S16)
65 			sampleFormat=paInt16;
66 #elif defined(SAMPLE_TYPE_FLOAT)
67 			sampleFormat=paFloat32;
68 #else
69 			#error unhandled SAMPLE_TYPE_xxx define
70 #endif
71 
72 
73 		// open a PortAudio stream
74 #ifdef ENABLE_PORTAUDIO_V19
75 		PaStreamParameters output = { gPortAudioOutputDevice,
76 			static_cast<int>(gDesiredOutputChannelCount),
77 			sampleFormat,
78 			Pa_GetDeviceInfo(gPortAudioOutputDevice)->defaultLowOutputLatency ,
79 			NULL};
80 
81 		err = Pa_OpenStream(
82 				&stream,
83 				NULL,
84 				&output,
85 				gDesiredOutputSampleRate,
86 				gDesiredOutputBufferSize * gDesiredOutputBufferCount,
87 				paClipOff|paDitherOff,
88 				CPortAudioSoundPlayer::PortAudioCallback,
89 				this);
90 
91 #else
92 		err = Pa_OpenStream(
93 			&stream,
94 			paNoDevice,			/* recording parameter, we're not recording */
95 			0,				/* recording parameter, we're not recording */
96 			paInt16,			/* recording parameter, we're not recording */
97 			NULL,
98 			gPortAudioOutputDevice,
99 			gDesiredOutputChannelCount,
100 			sampleFormat,
101 			NULL,
102 			gDesiredOutputSampleRate,
103 			gDesiredOutputBufferSize,
104 			gDesiredOutputBufferCount,
105 			paClipOff|paDitherOff,
106 			CPortAudioSoundPlayer::PortAudioCallback,
107 			this);
108 #endif
109 
110 		if(err!=paNoError)
111 			throw runtime_error(string(__func__)+" -- error opening PortAudio stream -- "+Pa_GetErrorText(err));
112 #warning test with some parameters that I know will fail
113 
114 				// ??? is PortAudio is not going to make the conversion if it cannot get this exact sample reate
115 				// and right now, there is no way in libportaudio to get the sample rate that was obtained... So
116 				// when they do implement being able to know at what sample rate the device was opened, I will query here
117 		devices[0].sampleRate=gDesiredOutputSampleRate; // make note of the sample rate for this device (??? which is only device zero for now)
118 		devices[0].channelCount=gDesiredOutputChannelCount; // make note of the number of channels for this device (??? which is only device zero for now)
119 
120 		// start the PortAudio stream
121 		err=Pa_StartStream(stream);
122 		if(err!=paNoError)
123 			throw runtime_error(string(__func__)+" -- error starting PortAudio stream -- "+Pa_GetErrorText(err));
124 
125 		supportsFullDuplex=false;
126 /* ??? implement this when/if possible
127 		supportsFullDuplex= query libportaudio if the device supports fullduplex mode ... perhaps write a method that runs prior to this point that tried to open the device with input and output parameters to Pa_OpenStream
128 */
129 
130 		ASoundPlayer::initialize();
131 		initialized=true;
132 		fprintf(stderr, "PortAudio player initialized\n");
133 	}
134 	else
135 		throw runtime_error(string(__func__)+" -- already initialized");
136 }
137 
deinitialize()138 void CPortAudioSoundPlayer::deinitialize()
139 {
140 	if(initialized)
141 	{
142 		PaError err;
143 
144 		ASoundPlayer::deinitialize();
145 
146 		// stop the PortAudio stream
147 		err=Pa_StopStream(stream);
148 		if(err!=paNoError)
149 			fprintf(stderr,"%s -- error starting PortAudio stream -- %s\n",__func__,Pa_GetErrorText(err));
150 
151 		// close PortAudio stream
152 		err=Pa_CloseStream(stream);
153 		if(err!=paNoError)
154 			fprintf(stderr,"%s -- error closing PortAudio stream -- %s\n",__func__,Pa_GetErrorText(err));
155 
156 		stream=NULL;
157 		initialized=false;
158 		fprintf(stderr, "PortAudio player deinitialized\n");
159 	}
160 }
161 
aboutToRecord()162 void CPortAudioSoundPlayer::aboutToRecord()
163 {
164 	// ??? Of course it would be nice to know if the record device about to be used is the same as this playback device...
165 	// I would just use the port audio device and make it a parameter to this method, except port audio may not been the API being
166 	// used... Perhaps what I should do is commit to a rule that if I'm using an API for playback, that I have to use the same API
167 	// for recording.. then I could use a void * parameter to this method and I would know it's an int index into PortAudio's known devices
168 	//  BUT... if I commited to this rule, then perhaps it would make sense to just merge ASoundPlayer and ASoundRecorder together
169 	// or maybe just into the same file so then only one file compiles and I could know that the device identification method was consistant
170 	if(!supportsFullDuplex)
171 		deinitialize();
172 }
173 
doneRecording()174 void CPortAudioSoundPlayer::doneRecording()
175 {
176 	if(!initialized && !supportsFullDuplex)
177 		initialize();
178 }
179 
180 
181 #ifdef ENABLE_PORTAUDIO_V19
PortAudioCallback(const void * inputBuffer,void * outputBuffer,unsigned long framesPerBuffer,const PaStreamCallbackTimeInfo * outTime,PaStreamCallbackFlags statusFlags,void * userData)182 int CPortAudioSoundPlayer::PortAudioCallback(const void *inputBuffer,void *outputBuffer,unsigned long framesPerBuffer,const PaStreamCallbackTimeInfo* outTime, PaStreamCallbackFlags statusFlags, void *userData)
183 #else
184 int CPortAudioSoundPlayer::PortAudioCallback(void *inputBuffer,void *outputBuffer,unsigned long framesPerBuffer,PaTimestamp outTime,void *userData)
185 #endif
186 {
187 	CPortAudioSoundPlayer* that = (CPortAudioSoundPlayer *)userData;
188 	if(!that->initialized) {
189 		return 0;
190 	}
191 
192 	try
193 	{
194 		// no conversion necessary because we initialized it with the type of sample_t (if portaudio didn't support our sample_t type natively then we would need to do conversion here)
195 		that->mixSoundPlayerChannels(gDesiredOutputChannelCount,(sample_t *)outputBuffer,framesPerBuffer);
196 	}
197 	catch(exception &e)
198 	{
199 		fprintf(stderr,"exception caught in play callback: %s\n",e.what());
200 	}
201 	catch(...)
202 	{
203 		fprintf(stderr,"unknown exception caught in play callback\n");
204 	}
205 	return 0;
206 }
207 
208 #endif // ENABLE_PORTAUDIO
209