static int instream_open_jack(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) { SoundIoInStream *instream = &is->pub; SoundIoInStreamJack *isj = &is->backend_data.jack; SoundIoJack *sij = &si->backend_data.jack; SoundIoDevice *device = instream->device; SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device; SoundIoDeviceJack *dj = &dev->backend_data.jack; if (sij->is_shutdown) return SoundIoErrorBackendDisconnected; if (!instream->name) instream->name = "SoundIoInStream"; instream->software_latency = device->software_latency_current; isj->period_size = sij->period_size; jack_status_t status; isj->client = jack_client_open(instream->name, JackNoStartServer, &status); if (!isj->client) { instream_destroy_jack(si, is); assert(!(status & JackInvalidOption)); if (status & JackShmFailure) return SoundIoErrorSystemResources; if (status & JackNoSuchClient) return SoundIoErrorNoSuchClient; return SoundIoErrorOpeningDevice; } int err; if ((err = jack_set_process_callback(isj->client, instream_process_callback, is))) { instream_destroy_jack(si, is); return SoundIoErrorOpeningDevice; } if ((err = jack_set_buffer_size_callback(isj->client, instream_buffer_size_callback, is))) { instream_destroy_jack(si, is); return SoundIoErrorOpeningDevice; } if ((err = jack_set_sample_rate_callback(isj->client, instream_sample_rate_callback, is))) { instream_destroy_jack(si, is); return SoundIoErrorOpeningDevice; } if ((err = jack_set_xrun_callback(isj->client, instream_xrun_callback, is))) { instream_destroy_jack(si, is); return SoundIoErrorOpeningDevice; } jack_on_shutdown(isj->client, instream_shutdown_callback, is); jack_nframes_t max_port_latency = 0; // register ports and map channels int connected_count = 0; for (int ch = 0; ch < instream->layout.channel_count; ch += 1) { SoundIoChannelId my_channel_id = instream->layout.channels[ch]; const char *channel_name = soundio_get_channel_name(my_channel_id); unsigned long flags = JackPortIsInput; if (!instream->non_terminal_hint) flags |= JackPortIsTerminal; jack_port_t *jport = jack_port_register(isj->client, channel_name, JACK_DEFAULT_AUDIO_TYPE, flags, 0); if (!jport) { instream_destroy_jack(si, is); return SoundIoErrorOpeningDevice; } SoundIoInStreamJackPort *isjp = &isj->ports[ch]; isjp->dest_port = jport; // figure out which source port this connects to SoundIoDeviceJackPort *djp = find_port_matching_channel(device, my_channel_id); if (djp) { isjp->source_port_name = djp->full_name; isjp->source_port_name_len = djp->full_name_len; connected_count += 1; max_port_latency = max(max_port_latency, djp->latency_range.max); } } // If nothing got connected, channel layouts aren't working. Just send the // data in the order of the ports. if (connected_count == 0) { max_port_latency = 0; instream->layout_error = SoundIoErrorIncompatibleDevice; int ch_count = min(instream->layout.channel_count, dj->port_count); for (int ch = 0; ch < ch_count; ch += 1) { SoundIoInStreamJackPort *isjp = &isj->ports[ch]; SoundIoDeviceJackPort *djp = &dj->ports[ch]; isjp->source_port_name = djp->full_name; isjp->source_port_name_len = djp->full_name_len; max_port_latency = max(max_port_latency, djp->latency_range.max); } } isj->hardware_latency = max_port_latency / (double)instream->sample_rate; return 0; }
static int port_connected(struct GenesisNode *node) { struct ResampleContext *resample_context = (struct ResampleContext *)node->userdata; if (!resample_context->in_connected || !resample_context->out_connected) return 0; struct GenesisPort *audio_in_port = node->ports[0]; struct GenesisPort *audio_out_port = node->ports[1]; int in_sample_rate = genesis_audio_port_sample_rate(audio_in_port); int out_sample_rate = genesis_audio_port_sample_rate(audio_out_port); int gcd = greatest_common_denominator(in_sample_rate, out_sample_rate); resample_context->upsample_factor = out_sample_rate / gcd; resample_context->downsample_factor = in_sample_rate / gcd; resample_context->oversampled_rate = in_sample_rate * resample_context->upsample_factor; if (in_sample_rate == out_sample_rate) { destroy(resample_context->impulse_response, resample_context->impulse_response_size); resample_context->impulse_response = nullptr; } else { double cutoff_freq_hz = min(in_sample_rate, out_sample_rate) / 2.0; double cutoff_freq_float = cutoff_freq_hz / (double)resample_context->oversampled_rate; double transition_band = transition_band_hz / resample_context->oversampled_rate; int window_size = ceil(4.0 / transition_band); window_size += !(window_size % 2); float *new_impulse_response = reallocate_safe(resample_context->impulse_response, resample_context->impulse_response_size, window_size); if (!new_impulse_response) return GenesisErrorNoMem; resample_context->impulse_response = new_impulse_response; resample_context->impulse_response_size = window_size; // create impulse response by sampling the sinc function and then // multiplying by the blackman window for (int i = 0; i < window_size; i += 1) { // sample the sinc function double sinc_sample = sinc(2.0 * cutoff_freq_float * (i - (window_size - 1) / 2.0)); double sample = sinc_sample * blackman_window(i, window_size); resample_context->impulse_response[i] = sample; } } // set up channel matrix const struct SoundIoChannelLayout * in_channel_layout = genesis_audio_port_channel_layout(audio_in_port); const struct SoundIoChannelLayout * out_channel_layout = genesis_audio_port_channel_layout(audio_out_port); memset(resample_context->channel_matrix, 0, sizeof(resample_context->channel_matrix)); int in_contains[GENESIS_CHANNEL_ID_COUNT]; int out_contains[GENESIS_CHANNEL_ID_COUNT]; for (int id = 0; id < GENESIS_CHANNEL_ID_COUNT; id += 1) { in_contains[id] = soundio_channel_layout_find_channel(in_channel_layout, (SoundIoChannelId)id); out_contains[id] = soundio_channel_layout_find_channel(out_channel_layout, (SoundIoChannelId)id); } bool unaccounted[GENESIS_CHANNEL_ID_COUNT]; for (int id = 0; id < GENESIS_CHANNEL_ID_COUNT; id += 1) { unaccounted[id] = in_contains[id] >= 0 && out_contains[id] == -1; } // route matching channel ids for (int id = 0; id < GENESIS_CHANNEL_ID_COUNT; id += 1) { if (in_contains[id] >= 0 && out_contains[id] >= 0) resample_context->channel_matrix[out_contains[id]][in_contains[id]] = 1.0; } // mix front center to left/right if (unaccounted[SoundIoChannelIdFrontCenter]) { if (out_contains[SoundIoChannelIdFrontLeft] >= 0 && out_contains[SoundIoChannelIdFrontRight] >= 0) { resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontLeft]][in_contains[SoundIoChannelIdFrontCenter]] += M_SQRT1_2; resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontRight]][in_contains[SoundIoChannelIdFrontCenter]] += M_SQRT1_2; unaccounted[SoundIoChannelIdFrontCenter] = false; } } // mix back left/right to back center, side or front if (unaccounted[SoundIoChannelIdBackLeft] && unaccounted[SoundIoChannelIdBackRight]) { if (out_contains[SoundIoChannelIdBackCenter] >= 0) { resample_context->channel_matrix[out_contains[SoundIoChannelIdBackCenter]][in_contains[SoundIoChannelIdBackLeft]] += M_SQRT1_2; resample_context->channel_matrix[out_contains[SoundIoChannelIdBackCenter]][in_contains[SoundIoChannelIdBackRight]] += M_SQRT1_2; unaccounted[SoundIoChannelIdBackLeft] = false; unaccounted[SoundIoChannelIdBackRight] = false; } else if (out_contains[SoundIoChannelIdSideLeft] >= 0 && out_contains[SoundIoChannelIdSideRight] >= 0) { if (in_contains[SoundIoChannelIdSideLeft] >= 0 && in_contains[SoundIoChannelIdSideRight] >= 0) { resample_context->channel_matrix[out_contains[SoundIoChannelIdSideLeft]][in_contains[SoundIoChannelIdBackLeft]] += M_SQRT1_2; resample_context->channel_matrix[out_contains[SoundIoChannelIdSideRight]][in_contains[SoundIoChannelIdBackRight]] += M_SQRT1_2; } else { resample_context->channel_matrix[out_contains[SoundIoChannelIdSideLeft]][in_contains[SoundIoChannelIdBackLeft]] += 1.0; resample_context->channel_matrix[out_contains[SoundIoChannelIdSideRight]][in_contains[SoundIoChannelIdBackRight]] += 1.0; } unaccounted[SoundIoChannelIdBackLeft] = false; unaccounted[SoundIoChannelIdBackRight] = false; } else if (out_contains[SoundIoChannelIdFrontLeft] >= 0 && out_contains[SoundIoChannelIdFrontRight] >= 0) { resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontLeft]][in_contains[SoundIoChannelIdBackLeft]] += surround_mix_level * M_SQRT1_2; resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontRight]][in_contains[SoundIoChannelIdBackRight]] += surround_mix_level * M_SQRT1_2; unaccounted[SoundIoChannelIdBackLeft] = false; unaccounted[SoundIoChannelIdBackRight] = false; } else if (out_contains[SoundIoChannelIdFrontCenter] >= 0) { resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontCenter]][in_contains[SoundIoChannelIdBackLeft]] += surround_mix_level * M_SQRT1_2; resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontCenter]][in_contains[SoundIoChannelIdBackRight]] += surround_mix_level * M_SQRT1_2; unaccounted[SoundIoChannelIdBackLeft] = false; unaccounted[SoundIoChannelIdBackRight] = false; } } // mix side left/right into back or front if (unaccounted[SoundIoChannelIdSideLeft] && unaccounted[SoundIoChannelIdSideRight]) { if (out_contains[SoundIoChannelIdBackLeft] >= 0 && out_contains[SoundIoChannelIdBackRight] >= 0) { // if back channels do not exist in the input, just copy side // channels to back channels, otherwise mix side into back if (in_contains[SoundIoChannelIdBackLeft] >= 0 && in_contains[SoundIoChannelIdBackRight] >= 0) { resample_context->channel_matrix[out_contains[SoundIoChannelIdBackLeft]][in_contains[SoundIoChannelIdSideLeft]] += M_SQRT1_2; resample_context->channel_matrix[out_contains[SoundIoChannelIdBackRight]][in_contains[SoundIoChannelIdSideRight]] += M_SQRT1_2; } else { resample_context->channel_matrix[out_contains[SoundIoChannelIdBackLeft]][in_contains[SoundIoChannelIdSideLeft]] += 1.0; resample_context->channel_matrix[out_contains[SoundIoChannelIdBackRight]][in_contains[SoundIoChannelIdSideRight]] += 1.0; } unaccounted[SoundIoChannelIdSideLeft] = false; unaccounted[SoundIoChannelIdSideRight] = false; } else if (out_contains[SoundIoChannelIdBackCenter] >= 0) { resample_context->channel_matrix[out_contains[SoundIoChannelIdBackCenter]][in_contains[SoundIoChannelIdSideLeft]] += M_SQRT1_2; resample_context->channel_matrix[out_contains[SoundIoChannelIdBackCenter]][in_contains[SoundIoChannelIdSideRight]] += M_SQRT1_2; unaccounted[SoundIoChannelIdSideLeft] = false; unaccounted[SoundIoChannelIdSideRight] = false; } else if (out_contains[SoundIoChannelIdFrontLeft] >= 0 && out_contains[SoundIoChannelIdFrontRight] >= 0) { resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontLeft]][in_contains[SoundIoChannelIdSideLeft]] += surround_mix_level; resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontRight]][in_contains[SoundIoChannelIdSideRight]] += surround_mix_level; unaccounted[SoundIoChannelIdSideLeft] = false; unaccounted[SoundIoChannelIdSideRight] = false; } else if (out_contains[SoundIoChannelIdFrontCenter] >= 0) { resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontCenter]][in_contains[SoundIoChannelIdSideLeft]] += surround_mix_level; resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontCenter]][in_contains[SoundIoChannelIdSideRight]] += surround_mix_level; unaccounted[SoundIoChannelIdSideLeft] = false; unaccounted[SoundIoChannelIdSideRight] = false; } } // mix left of center/right of center into front left/right or center if (unaccounted[SoundIoChannelIdFrontLeftCenter] && unaccounted[SoundIoChannelIdFrontRightCenter]) { if (out_contains[SoundIoChannelIdFrontLeft] >= 0 && out_contains[SoundIoChannelIdFrontRight] >= 0) { resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontLeft]][in_contains[SoundIoChannelIdFrontLeftCenter]] += 1.0; resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontRight]][in_contains[SoundIoChannelIdFrontRightCenter]] += 1.0; unaccounted[SoundIoChannelIdFrontLeftCenter] = false; unaccounted[SoundIoChannelIdFrontRightCenter] = false; } else if (out_contains[SoundIoChannelIdFrontCenter] >= 0) { resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontCenter]][in_contains[SoundIoChannelIdFrontLeftCenter]] += M_SQRT1_2; resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontCenter]][in_contains[SoundIoChannelIdFrontRightCenter]] += M_SQRT1_2; unaccounted[SoundIoChannelIdFrontLeftCenter] = false; unaccounted[SoundIoChannelIdFrontRightCenter] = false; } } // mix LFE into front left/right or center if (unaccounted[SoundIoChannelIdLfe]) { if (out_contains[SoundIoChannelIdFrontCenter] >= 0) { resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontCenter]][in_contains[SoundIoChannelIdLfe]] += lfe_mix_level; unaccounted[SoundIoChannelIdLfe] = false; } else if (out_contains[SoundIoChannelIdFrontLeft] >= 0 && out_contains[SoundIoChannelIdFrontRight] >= 0) { resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontLeft]][in_contains[SoundIoChannelIdLfe]] += lfe_mix_level * M_SQRT1_2; resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontRight]][in_contains[SoundIoChannelIdLfe]] += lfe_mix_level * M_SQRT1_2; unaccounted[SoundIoChannelIdLfe] = false; } } // mix front left/right to front center if (unaccounted[SoundIoChannelIdFrontLeft] && unaccounted[SoundIoChannelIdFrontRight]) { if (out_contains[SoundIoChannelIdFrontCenter] >= 0) { resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontCenter]][in_contains[SoundIoChannelIdFrontLeft]] += 0.5; resample_context->channel_matrix[out_contains[SoundIoChannelIdFrontCenter]][in_contains[SoundIoChannelIdFrontRight]] += 0.5; unaccounted[SoundIoChannelIdFrontLeft] = false; unaccounted[SoundIoChannelIdFrontRight] = false; } } // make sure all input channels are accounted for for (int id = 0; id < GENESIS_CHANNEL_ID_COUNT; id += 1) { if (unaccounted[id]) { fprintf(stderr, "unaccounted: %s\n", soundio_get_channel_name((SoundIoChannelId)id)); return GenesisErrorUnimplemented; } } // normalize per channel for (int out_ch = 0; out_ch < out_channel_layout->channel_count; out_ch += 1) { double sum = 0.0; for (int in_ch = 0; in_ch < in_channel_layout->channel_count; in_ch += 1) { sum += resample_context->channel_matrix[out_ch][in_ch]; } if (sum > 0.0) { for (int in_ch = 0; in_ch < in_channel_layout->channel_count; in_ch += 1) { resample_context->channel_matrix[out_ch][in_ch] *= 1.0 / sum; } } } return 0; }