1 /*
2     TiMidity++ -- MIDI to WAVE converter and player
3     Copyright (C) 1999-2002 Masanao Izumo <mo@goice.co.jp>
4     Copyright (C) 1995 Tuukka Toivonen <tt@cgs.fi>
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but 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     hpux_audio.c written by Vincent Pagel pagel@loria.fr
21 
22     Functions to play sound on HPUX stations V0.1 1995 March 1
23 
24     HPUX allows you to connect to a remote sound server through a socket
25     ( put the name in the string "server"). Not compulsory to play the
26     sound on the machine running timidity
27 
28     Exemple : if I'm on the console of 'exupery' and that I've opened a
29     remote connection to 'yeager' , the command line becomes :
30 
31     yeager 1% timidity  -o exupery -Od jazzy.mid
32 
33     And the sound travels through the net !
34 
35 */
36 
37 #ifdef HAVE_CONFIG_H
38 #include "config.h"
39 #endif /* HAVE_CONFIG_H */
40 #include <stdlib.h>
41 #include <stdio.h>
42 #include <fcntl.h>
43 #include <unistd.h>
44 #include <time.h>
45 #include <sys/socket.h>
46 #include <netinet/in.h>
47 #include <Alib.h>
48 #include <CUlib.h>
49 
50 #include "timidity.h"
51 #include "common.h"
52 #include "output.h"
53 #include "controls.h"
54 #include "timer.h"
55 #include "instrum.h"
56 #include "playmidi.h"
57 #include "miditrace.h"
58 
59 static int open_output(void); /* 0=success, 1=warning, -1=fatal error */
60 static void close_output(void);
61 static int output_data(char *buf, int32 nbytes);
62 static int acntl(int request, void *arg);
63 
64 static Audio    *audio;		      /* Audio Connection */
65 static AErrorHandler   prevHandler;  /* pointer to previous error handler */
66 static AudioAttributes  SourceAttribs, PlayAttribs;
67 static AudioAttrMask   SourceAttribsMask, PlayAttribsMask;
68 static AGainEntry      gainEntry[4];
69 static ATransID  xid;	/* Socket for connection with audio stream */
70 static SStream audioStream;
71 static SSPlayParams    streamParams;
72 static int streamSocket;
73 static long status;
74 static int data_format;
75 static long   seekOffset, data_length;
76 static AByteOrder      byte_order, play_byte_order;
77 static int useIntSpeaker;
78 
79 /* export the playback mode */
80 #define DEFAULT_HP_ENCODING PE_16BIT|PE_SIGNED
81 
82 #define dpm hpux_nplay_mode
83 PlayMode dpm = {
84     DEFAULT_RATE, DEFAULT_HP_ENCODING, PF_PCM_STREAM|PF_CAN_TRACE,
85     -1,
86     {0}, /* default: get all the buffer fragments you can */
87     "HPUX network audio (Alib)", 'A',
88     "", /* THIS STRING IS THE NAME OF THE AUDIO SERVER (default =none)*/
89 
90     open_output,
91     close_output,
92     output_data,
93     acntl
94 };
95 
96 /*
97  * error handler for player
98  */
myHandler(Audio * audio,AErrorEvent * err_event)99 long myHandler( Audio  * audio, AErrorEvent  * err_event  )
100 {
101     char    errorbuff[132];
102 
103     AGetErrorText(audio, err_event->error_code, errorbuff, 131);
104     ctl->cmsg(CMSG_ERROR,VERB_NORMAL,"HPUX Audio error:%s", errorbuff );
105     ctl->close();
106     exit(1);
107 }
108 
open_output(void)109 static int open_output(void)
110 {
111     char *pSpeaker;	/* Environment SPEAKER variable */
112     int warnings=0;
113     int i;
114 
115     if(dpm.encoding & PE_ALAW)
116     {
117 	ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
118 		  "%s: A-Law not supported in this version", dpm.name);
119 	return -1;
120     }
121 
122 /* replace default error handler */
123     prevHandler = ASetErrorHandler(myHandler);
124 
125 /*
126  *  open audio connection
127  */
128     audio = AOpenAudio( dpm.name, NULL );
129 
130     PlayAttribsMask = 0;
131     SourceAttribsMask = 0;
132 
133     /* User defined sample rate */
134     SourceAttribs.attr.sampled_attr.sampling_rate = dpm.rate;
135     SourceAttribsMask = (PlayAttribsMask | ASSamplingRateMask);
136     ctl->cmsg(CMSG_INFO, VERB_DEBUG, "Playing rate : %i", dpm.rate);
137 
138     /* User wants mono or Stereo ?  */
139     SourceAttribs.attr.sampled_attr.channels = (dpm.encoding & PE_MONO) ? 1 : 2;
140     SourceAttribsMask = (SourceAttribsMask | ASChannelsMask);
141     if (dpm.encoding & PE_MONO)
142 	ctl->cmsg(CMSG_INFO, VERB_DEBUG, "Sound is mono");
143     else
144 	ctl->cmsg(CMSG_INFO, VERB_DEBUG, "Sound is stereo dolby fx");
145 
146     if (dpm.encoding & PE_ULAW )
147 	{ data_format= AFFRawMuLaw; /* Ignore the rest signed/unsigned 16/8 */
148 	ctl->cmsg(CMSG_INFO, VERB_DEBUG, "Sound format Ulaw");
149 	}
150     else if (dpm.encoding & PE_16BIT )
151 	{ /* HP700's DO NOT SUPPORT unsigned 16bits */
152 	    if (! (dpm.encoding & PE_SIGNED))
153 		{ ctl->cmsg(CMSG_WARNING, VERB_NORMAL,"No unsigned 16bit format");
154 		dpm.encoding |= PE_SIGNED;
155 		warnings=1;
156 		}
157 	     ctl->cmsg(CMSG_INFO, VERB_DEBUG, "Sound format Linear 16bits");
158 	    data_format= AFFRawLin16;
159 	}
160     else
161 	{ if (dpm.encoding & PE_SIGNED)
162 	   {
163 	       data_format=AFFRawLin8;
164 	       ctl->cmsg(CMSG_INFO, VERB_DEBUG, "Sound format Linear signed 8bits");
165 	   }
166 	else
167 	  {
168 	      data_format=AFFRawLin8Offset;
169 	      ctl->cmsg(CMSG_INFO, VERB_DEBUG, "Sound format Linear unsigned 8bits");
170 	  }
171 	};
172 
173     AChooseSourceAttributes(audio, NULL,NULL, data_format,
174 			    SourceAttribsMask, &SourceAttribs,
175 			    &seekOffset,&data_length, &byte_order, NULL );
176 
177     AChoosePlayAttributes(audio, &SourceAttribs, PlayAttribsMask,
178 			  &PlayAttribs, &play_byte_order,NULL);
179 
180     /* Match the source and play audio parameters and see if all are accepted */
181     if (PlayAttribs.attr.sampled_attr.sampling_rate!=
182 	SourceAttribs.attr.sampled_attr.sampling_rate)
183 	{ ctl->cmsg(CMSG_WARNING, VERB_NORMAL,"Unsupported sample rate %i replaced by %i",
184 	       SourceAttribs.attr.sampled_attr.sampling_rate,
185 	       PlayAttribs.attr.sampled_attr.sampling_rate );
186 	warnings=1;
187 	dpm.rate = PlayAttribs.attr.sampled_attr.sampling_rate;
188 	}
189 
190     if (PlayAttribs.attr.sampled_attr.channels!=
191 	SourceAttribs.attr.sampled_attr.channels)
192 	{ ctl->cmsg(CMSG_WARNING, VERB_NORMAL,"Unsupported STEREO -> going back mono");
193 	dpm.encoding |= PE_MONO;
194 	warnings=1;
195 	}
196 
197     if (PlayAttribs.attr.sampled_attr.data_format !=
198 	SourceAttribs.attr.sampled_attr.data_format )
199 	{ ctl->cmsg(CMSG_ERROR, VERB_NORMAL,"Audio device can't play this format, try another one");
200 	return -1;
201 	}
202 
203     /*
204      * Traditionnaly on HPUX, the SPEAKER environment variable is EXTERNAL if we use
205      * the headphone jack and INTERNAL if we use the internal speaker
206      */
207     pSpeaker = getenv( "SPEAKER" );         /* get user speaker preference */
208     if ( pSpeaker )
209 	useIntSpeaker = ( (*pSpeaker == 'i') || (*pSpeaker == 'I') );
210     else
211 	/* SPEAKER environment variable not found - use internal speaker */
212 	useIntSpeaker = 1;
213 
214     /* Tune the stereo */
215     switch(PlayAttribs.attr.sampled_attr.channels )
216 	{
217 	case 1:
218 	    gainEntry[0].u.o.out_ch = AOCTMono;
219 	    gainEntry[0].gain = AUnityGain;
220 	    gainEntry[0].u.o.out_dst = AODTDefaultOutput;
221 	    break;
222 	case 2:
223 	default:    /* assume no more than 2 channels... for the moment !!! */
224 	    gainEntry[0].u.o.out_ch = AOCTLeft;
225 	    gainEntry[0].gain = AUnityGain;
226 	    gainEntry[0].u.o.out_dst = AODTDefaultOutput;
227 	    gainEntry[1].u.o.out_ch = AOCTRight;
228 	    gainEntry[1].gain = AUnityGain;
229 	    gainEntry[1].u.o.out_dst = AODTDefaultOutput;
230 	    break;
231 	}
232     streamParams.gain_matrix.type = AGMTOutput;       /* gain matrix */
233     streamParams.gain_matrix.num_entries = PlayAttribs.attr.sampled_attr.channels;
234     streamParams.gain_matrix.gain_entries = gainEntry;
235     streamParams.play_volume = AUnityGain;            /* play volume */
236     streamParams.priority = APriorityNormal;          /* normal priority */
237     streamParams.event_mask = 0;                      /* don't solicit any events */
238 
239 /* create an audio stream */
240     xid = APlaySStream( audio, ~0, &PlayAttribs, &streamParams,
241 			&audioStream, NULL );
242 /* create a stream socket */
243     streamSocket = socket( AF_INET, SOCK_STREAM, 0 );
244     if( streamSocket < 0 )
245 	{ ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Audio Socket creation failed" );
246 	return -1;
247 	}
248 
249     i = 128 * 1024;
250     status = setsockopt(streamSocket, SOL_SOCKET, SO_SNDBUF, &i, sizeof(int));
251     if(status < 0)
252     {
253 	ctl->cmsg(CMSG_WARNING, VERB_NORMAL,
254 		  "Warning: SO_SNDBUF: size=%d is failed", i);
255 	warnings=1;
256     }
257 
258 /* connect the stream socket to the audio stream port */
259     status = connect( streamSocket, (struct sockaddr *)&audioStream.tcp_sockaddr,
260 		      sizeof(struct sockaddr_in) );
261     if(status<0)
262 	{  ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Audio Connect failed" );
263 	return -1;
264 	}
265     dpm.fd=0;
266     return(warnings);
267 }
268 
output_data(char * buf,int32 nbytes)269 static int output_data(char *buf, int32 nbytes)
270 {
271     return write( streamSocket, buf, nbytes );
272 }
273 
close_output(void)274 static void close_output(void)
275 {
276     if(dpm.fd != -1)
277     {
278 	close( streamSocket );
279 	ASetCloseDownMode( audio, AKeepTransactions, NULL );
280 	ACloseAudio( audio, NULL );
281 	dpm.fd = -1;
282     }
283 }
284 
acntl(int request,void * arg)285 static int acntl(int request, void *arg)
286 {
287     switch(request)
288     {
289       case PM_REQ_DISCARD:
290       case PM_REQ_PLAY_START: /* Called just before playing */
291       case PM_REQ_PLAY_END: /* Called just after playing */
292         return 0;
293     }
294     return -1;
295 }
296