1 /*
2  * portaudio_drv.c - PortAudio audio input driver.
3  *
4  * Written by
5  *  Marco van den Heuvel <blackystardust68@yahoo.com>
6  *
7  * This file is part of VICE, the Versatile Commodore Emulator.
8  * See README for copyright notice.
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2 of the License, or
13  *  (at your option) any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program; if not, write to the Free Software
22  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
23  *  02111-1307  USA.
24  *
25  */
26 
27 #include "vice.h"
28 
29 #include <stdlib.h>
30 #include <string.h>
31 
32 #include "lib.h"
33 #include "log.h"
34 #include "machine.h"
35 #include "maincpu.h"
36 #include "portaudio_drv.h"
37 #include "sampler.h"
38 
39 #ifdef USE_PORTAUDIO
40 #include <portaudio.h>
41 
42 static log_t portaudio_log = LOG_ERR;
43 
44 static int stream_started = 0;
45 static PaStream *stream = NULL;
46 
47 static unsigned int old_frame;
48 static unsigned int sound_frames_per_sec;
49 static unsigned int sound_cycles_per_frame;
50 static unsigned int sound_samples_per_frame;
51 static unsigned int same_sample = 0;
52 
53 static int current_channels = 0;
54 
55 static uint16_t *stream_buffer = NULL;
56 static uint8_t old_sample = 0x80;
57 
portaudio_start_stream(void)58 static void portaudio_start_stream(void)
59 {
60     PaStreamParameters inputParameters;
61     PaError err = paNoError;
62 
63     inputParameters.device = Pa_GetDefaultInputDevice();
64     if (inputParameters.device != paNoDevice) {
65         inputParameters.channelCount = current_channels;
66         inputParameters.sampleFormat = paInt16;
67         inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultHighInputLatency ;
68         inputParameters.hostApiSpecificStreamInfo = NULL;
69         sound_cycles_per_frame = machine_get_cycles_per_frame();
70         sound_frames_per_sec = machine_get_cycles_per_second() / sound_cycles_per_frame;
71         sound_samples_per_frame = 44100 / sound_frames_per_sec;
72         err = Pa_OpenStream(&stream, &inputParameters, NULL, 44100, sound_samples_per_frame, paClipOff, NULL, NULL);
73         if (err == paNoError) {
74             err = Pa_StartStream(stream);
75             if (err == paNoError) {
76                 stream_started = 1;
77                 stream_buffer = lib_malloc(sound_samples_per_frame * 2 * current_channels);
78                 memset(stream_buffer, 0, sound_samples_per_frame * 2 * current_channels);
79                 old_frame = (maincpu_clk / sound_cycles_per_frame) + 1;
80             } else {
81                 log_error(portaudio_log, "Could not start stream");
82             }
83         } else {
84             log_error(portaudio_log, "Could not open stream");
85         }
86     } else {
87         log_error(portaudio_log, "Could not find a default input device");
88     }
89 }
90 
portaudio_stop_stream(void)91 static void portaudio_stop_stream(void)
92 {
93     Pa_AbortStream(stream);
94     Pa_CloseStream(stream);
95     stream = NULL;
96     if (stream_buffer) {
97         lib_free(stream_buffer);
98         stream_buffer = NULL;
99     }
100     stream_started = 0;
101 }
102 
portaudio_start_sampling(int channels)103 static void portaudio_start_sampling(int channels)
104 {
105     PaError err = paNoError;
106 
107     if (stream_started) {
108         log_error(portaudio_log, "Attempted to start portaudio twice");
109     } else {
110 
111         err = Pa_Initialize();
112 
113         if (err == paNoError ) {
114             current_channels = channels;
115             portaudio_start_stream();
116         } else {
117             log_error(portaudio_log, "Could not init portaudio");
118         }
119     }
120 }
121 
portaudio_stop_sampling(void)122 static void portaudio_stop_sampling(void)
123 {
124     portaudio_stop_stream();
125     Pa_Terminate();
126 }
127 
portaudio_get_sample(int channel)128 static uint8_t portaudio_get_sample(int channel)
129 {
130     unsigned int current_frame;
131     unsigned int current_cycle;
132     unsigned int frame_diff;
133     unsigned int frame_sample;
134 
135     if (!stream_buffer) {
136         return 0x80;
137     }
138     current_frame = maincpu_clk / sound_cycles_per_frame;
139     current_cycle = maincpu_clk % sound_cycles_per_frame;
140 
141     if (current_frame > old_frame) {
142         frame_diff = current_frame - old_frame;
143         while (frame_diff) {
144             --frame_diff;
145             ++old_frame;
146             if (Pa_GetStreamReadAvailable(stream) >= sound_samples_per_frame) {
147                 Pa_ReadStream(stream, stream_buffer, sound_samples_per_frame);
148                 same_sample = 0;
149             } else {
150                 ++same_sample;
151                 if (same_sample >= sound_samples_per_frame) {
152                     same_sample = 0;
153                     portaudio_stop_stream();
154                     portaudio_start_stream();
155                     log_warning(portaudio_log, "Had to restart the stream");
156                 }
157                 return old_sample;
158             }
159         }
160     }
161     frame_sample = current_cycle * sound_samples_per_frame / sound_cycles_per_frame;
162 
163     switch (channel) {
164         case SAMPLER_CHANNEL_1:
165             old_sample = (uint8_t)((stream_buffer[frame_sample * 2] >> 8) + 0x80);
166             break;
167         case SAMPLER_CHANNEL_2:
168             old_sample = (uint8_t)((stream_buffer[(frame_sample * 2) + 1] >> 8) + 0x80);
169             break;
170         case SAMPLER_CHANNEL_DEFAULT:
171         default:
172             old_sample = (uint8_t)((stream_buffer[frame_sample] >> 8) + 0x80);
173             break;
174     }
175 
176     return old_sample;
177 }
178 
portaudio_shutdown(void)179 static void portaudio_shutdown(void)
180 {
181     if (stream_started) {
182         portaudio_stop_sampling();
183     }
184 }
185 
186 static sampler_device_t portaudio_device =
187 {
188     "portaudio based hardware audio input",
189     portaudio_start_sampling,
190     portaudio_stop_sampling,
191     portaudio_get_sample,
192     portaudio_shutdown,
193     NULL, /* no resources */
194     NULL, /* no cmdline options */
195     NULL  /* no reset */
196 };
197 
198 
portaudio_init(void)199 void portaudio_init(void)
200 {
201     portaudio_log = log_open("Sampler PortAudio");
202 
203     sampler_device_register(&portaudio_device, SAMPLER_DEVICE_PORTAUDIO);
204 }
205 #endif
206