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