diff --git a/zyngine/zynthian_engine_audioplayer.py b/zyngine/zynthian_engine_audioplayer.py index d31ff5fb6..7d9bd5a50 100644 --- a/zyngine/zynthian_engine_audioplayer.py +++ b/zyngine/zynthian_engine_audioplayer.py @@ -94,7 +94,6 @@ def start(self): def stop(self): try: zynaudioplayer.stop() - zynaudioplayer = None zynsigman.unregister(zynsigman.S_AUDIO_RECORDER, zynthian_audio_recorder.SS_AUDIO_RECORDER_STATE, self.update_rec) except Exception as e: logging.error("Failed to close audio player: %s", e) @@ -271,7 +270,7 @@ def set_preset(self, processor, preset, preload=False): self._ctrls = [ ['gain', None, gain, 2.0], ['record', None, record, ['stopped', 'recording']], - ['loop', None, loop, ['one-shot', 'looping']], + ['loop', None, loop, ['one-shot', 'looping', 'play-all', 'toggle']], ['transport', None, transport, ['stopped', 'playing']], ['position', None, 0.0, dur], ['left track', None, default_a, [track_labels, track_values]], @@ -391,7 +390,7 @@ def control_cb(self, handle, id, value): elif id == 3: ctrl_dict['gain'].set_value(value, False) elif id == 4: - ctrl_dict['loop'].set_value(int(value) * 64, False) + ctrl_dict['loop'].set_value(int(value), False) elif id == 5: ctrl_dict['left track'].set_value(int(value), False) elif id == 6: @@ -558,7 +557,6 @@ def update_rec(self, state): # Specific functions # --------------------------------------------------------------------------- - # --------------------------------------------------------------------------- # API methods # --------------------------------------------------------------------------- diff --git a/zyngui/zynthian_gui_admin.py b/zyngui/zynthian_gui_admin.py index ca9624af2..8f46dd350 100644 --- a/zyngui/zynthian_gui_admin.py +++ b/zyngui/zynthian_gui_admin.py @@ -109,6 +109,12 @@ def fill_list(self): else: self.list_data.append((self.toggle_midi_sys, 0, "\u2610 MIDI System Messages")) + transpose = lib_zyncore.get_global_transpose() + if transpose > 0: + self.list_data.append((self.global_transpose, transpose, f"[+{transpose}] Global Transpose")) + else: + self.list_data.append((self.global_transpose, transpose, f"[{transpose}] Global Transpose")) + self.list_data.append((None, 0, "> AUDIO")) if self.state_manager.allow_rbpi_headphones(): @@ -179,6 +185,7 @@ def select_action(self, i, t='S'): def set_select_path(self): self.select_path.set("Admin") + self.set_title("Admin") #TODO: Should not need to set title and select_path! def execute_commands(self): self.state_manager.start_busy("admin_commands") @@ -351,6 +358,15 @@ def toggle_midi_sys(self): lib_zyncore.set_midi_filter_system_events(zynthian_gui_config.midi_sys_enabled) self.fill_list() + def global_transpose(self): + self.enable_param_editor(self, "Global Transpose", {'value_min':-24, 'value_max':24, 'value':lib_zyncore.get_global_transpose()}) + + def send_controller_value(self, zctrl): + """ Handle param editor""" + if zctrl.symbol == "Global Transpose": + lib_zyncore.set_global_transpose(zctrl.value) + self.fill_list() + def toggle_usbmidi_by_port(self): if os.environ.get("ZYNTHIAN_USB_MIDI_BY_PORT", "0") == "1": os.environ["ZYNTHIAN_USB_MIDI_BY_PORT"] = "0" diff --git a/zyngui/zynthian_gui_selector.py b/zyngui/zynthian_gui_selector.py index af467ebdc..ec60ed6db 100644 --- a/zyngui/zynthian_gui_selector.py +++ b/zyngui/zynthian_gui_selector.py @@ -349,6 +349,8 @@ def select_action(self, index, t='S'): #-------------------------------------------------------------------------- def zynpot_cb(self, i, dval): + if super().zynpot_cb(i, dval): + return if self.shown and self.zselector and self.zselector.index == i: self.zselector.zynpot_cb(dval) if self.index != self.zselector.zctrl.value: diff --git a/zynlibs/zynaudioplayer/audio_player.h b/zynlibs/zynaudioplayer/audio_player.h index fa0effc17..db5fe4a60 100644 --- a/zynlibs/zynaudioplayer/audio_player.h +++ b/zynlibs/zynaudioplayer/audio_player.h @@ -64,7 +64,7 @@ class AUDIO_PLAYER { uint8_t play_state = STOPPED; // Current playback state (STOPPED|STARTING|PLAYING|STOPPING) sf_count_t file_read_pos = 0; // Current file read position (frames) - uint8_t loop = 0; // 1 to loop at end of song + uint8_t loop = 0; // 1 to loop at end of song, 2 to play once but ignore note-off bool looped = false; // True if started playing a loop (not first time) sf_count_t loop_start = 0; // Start of loop in frames from start of file sf_count_t loop_start_src = -1; // Start of loop in frames from start after SRC diff --git a/zynlibs/zynaudioplayer/player.cpp b/zynlibs/zynaudioplayer/player.cpp index e81f2240e..9461c3bd5 100644 --- a/zynlibs/zynaudioplayer/player.cpp +++ b/zynlibs/zynaudioplayer/player.cpp @@ -387,7 +387,7 @@ void* file_thread_fn(void * param) { bool bReverse = (pPlayer->varispeed < 0.0); if(bReverse) { - if(pPlayer->loop) { + if(pPlayer->loop == 1) { // Limit read to loop range if(pPlayer->file_read_pos <= pPlayer->loop_start) nMaxFrames = 0; @@ -398,7 +398,7 @@ void* file_thread_fn(void * param) { nMaxFrames = pPlayer->file_read_pos - pPlayer->crop_start; } } else { - if(pPlayer->loop) { + if(pPlayer->loop == 1) { // Limit read to loop range if(pPlayer->file_read_pos >= pPlayer->loop_end) nMaxFrames = 0; @@ -437,7 +437,7 @@ void* file_thread_fn(void * param) { } } else - pPlayer->file_read_pos += nFramesRead = sf_readf_float(pFile, pBufferOut, nMaxFrames); + pPlayer->file_read_pos += (nFramesRead = sf_readf_float(pFile, pBufferOut, nMaxFrames)); } else { // Populate SRC input buffer before SRC process if(bReverse) { @@ -459,7 +459,7 @@ void* file_thread_fn(void * param) { } } else - pPlayer->file_read_pos += nFramesRead = sf_readf_float(pFile, pBufferIn + nUnusedFrames * pPlayer->sf_info.channels, nMaxFrames); + pPlayer->file_read_pos += (nFramesRead = sf_readf_float(pFile, pBufferIn + nUnusedFrames * pPlayer->sf_info.channels, nMaxFrames)); } getMutex(); @@ -473,15 +473,15 @@ void* file_thread_fn(void * param) { // We need to perform SRC on this block of code srcData.input_frames = nFramesRead; int rc = src_process(pSrcState, &srcData); - nUnusedFrames = nFramesRead - srcData.input_frames_used; - nFramesRead = srcData.output_frames_gen; if(rc) { DPRINTF("SRC failed with error %d, %lu frames generated\n", nFramesRead, srcData.output_frames_gen); } else { DPRINTF("SRC suceeded - %lu frames generated, %lu frames used, %lu frames unused\n", srcData.output_frames_gen, srcData.input_frames_used, nUnusedFrames); + nUnusedFrames = nFramesRead - srcData.input_frames_used; + nFramesRead = srcData.output_frames_gen; + // Shift unused samples to start of buffer + memcpy(pBufferIn, pBufferIn + srcData.input_frames_used * sizeof(float) * pPlayer->sf_info.channels, nUnusedFrames * sizeof(float) * pPlayer->sf_info.channels); } - // Shift unused samples to start of buffer - memcpy(pBufferIn, pBufferIn + srcData.input_frames_used * sizeof(float) * pPlayer->sf_info.channels, nUnusedFrames * sizeof(float) * pPlayer->sf_info.channels); } else { //DPRINTF("No SRC, read %u frames\n", nFramesRead); } @@ -518,7 +518,7 @@ void* file_thread_fn(void * param) { break; } } - } else if(pPlayer->loop) { + } else if(pPlayer->loop == 1) { // Short read - looping so fill from loop start point in file pPlayer->file_read_status = LOOPING; //srcData.end_of_input = 1; @@ -736,12 +736,12 @@ float get_position(AUDIO_PLAYER * pPlayer) { return 0.0; } -void enable_loop(AUDIO_PLAYER * pPlayer, uint8_t bLoop) { +void enable_loop(AUDIO_PLAYER * pPlayer, uint8_t nLoop) { if(!pPlayer) return; getMutex(); - pPlayer->loop = bLoop; - if(bLoop && pPlayer->play_pos_frames > pPlayer->loop_end_src) + pPlayer->loop = nLoop; + if(nLoop && pPlayer->play_pos_frames > pPlayer->loop_end_src) pPlayer->play_pos_frames = pPlayer->loop_start_src; pPlayer->file_read_status = SEEKING; releaseMutex(); @@ -759,7 +759,7 @@ void set_loop_start_time(AUDIO_PLAYER * pPlayer, float time) { getMutex(); pPlayer->loop_start = frames; pPlayer->loop_start_src = pPlayer->loop_start * pPlayer->src_ratio; - if(pPlayer->loop && pPlayer->looped) + if(pPlayer->loop == 1 && pPlayer->looped) pPlayer->file_read_status = SEEKING; releaseMutex(); pPlayer->last_loop_start = -1; @@ -783,7 +783,7 @@ void set_loop_end_time(AUDIO_PLAYER * pPlayer, float time) { getMutex(); pPlayer->loop_end = frames; pPlayer->loop_end_src = pPlayer->loop_end * pPlayer->src_ratio; - if(pPlayer->loop && pPlayer->looped) + if(pPlayer->loop == 1 && pPlayer->looped) pPlayer->file_read_status = SEEKING; releaseMutex(); pPlayer->last_loop_end = -1; @@ -1218,8 +1218,9 @@ int on_jack_process(jack_nframes_t nFrames, void * arg) { float* output_buffers[] = {pOutA, pOutB}; bool bReverse = pPlayer->varispeed < 0.0; - if(pPlayer->play_state == STARTING && pPlayer->file_read_status != SEEKING) + if(pPlayer->play_state == STARTING && pPlayer->file_read_status != SEEKING) { pPlayer->play_state = PLAYING; + } if(pPlayer->play_state == PLAYING || pPlayer->play_state == STOPPING) { if (pPlayer->time_ratio_dirty) { @@ -1234,6 +1235,7 @@ int on_jack_process(jack_nframes_t nFrames, void * arg) { pPlayer->time_ratio_dirty = false; } while(pPlayer->stretcher->available() < nFrames) { + // Process data from fifo until sufficient to populate this frame (first attempt may give -1 but that's okay as we will repeat) size_t sampsReq = min((size_t)256, pPlayer->stretcher->getSamplesRequired()); size_t nBytes = min(jack_ringbuffer_read_space(pPlayer->ringbuffer_a), jack_ringbuffer_read_space(pPlayer->ringbuffer_b)); nBytes = min(nBytes, sampsReq * sizeof(float)); @@ -1244,11 +1246,11 @@ int on_jack_process(jack_nframes_t nFrames, void * arg) { // stretch pPlayer->stretcher->process(stretch_input_buffers, nRead / sizeof(float), nRead != nBytes); if(nRead == 0) - break; + break; // fifo buffers run dry } a_count = min(pPlayer->stretcher->available(), (int)nFrames); if(a_count < 0) - a_count = 0; + a_count = 0; // If stretcher gives fault it will respond with -1 a_count = pPlayer->stretcher->retrieve(output_buffers, a_count); if(pPlayer->held_note != pPlayer->env_gate) set_env_gate(pPlayer, pPlayer->held_note); @@ -1266,6 +1268,7 @@ int on_jack_process(jack_nframes_t nFrames, void * arg) { pOutB[offset] *= pPlayer->gain; } } + // Advance play position based on the raw (SRC'd) frames if(bReverse) pPlayer->play_pos_frames -= r_count; else @@ -1277,17 +1280,18 @@ int on_jack_process(jack_nframes_t nFrames, void * arg) { if(cue_point_play > cue && pPlayer->play_pos_frames > pPlayer->cue_points[cue].offset) { pPlayer->play_pos_frames = pPlayer->cue_points[cue - 1].offset; pPlayer->env_state = ENV_RELEASE; //!@todo This looks wrong - if(pPlayer->loop) + if(pPlayer->loop == 1) pPlayer->file_read_status = SEEKING; - else + else { pPlayer->play_state = STOPPING; + } } else if(a_count < nFrames && pPlayer->file_read_status == IDLE) { // Reached end of file pPlayer->play_pos_frames = pPlayer->crop_start_src; pPlayer->play_state = STOPPING; } } else { - if(pPlayer->loop) { + if(pPlayer->loop == 1) { if(bReverse) { if(pPlayer->play_pos_frames <= pPlayer->loop_start_src) { size_t i = pPlayer->loop_start_src - pPlayer->play_pos_frames; @@ -1301,38 +1305,46 @@ int on_jack_process(jack_nframes_t nFrames, void * arg) { } } } else if(a_count < nFrames && pPlayer->file_read_status == IDLE) { - // Reached end of file + // No more data from file reader, e.g. reached end of file if(bReverse) pPlayer->play_pos_frames = pPlayer->crop_end_src; else pPlayer->play_pos_frames = pPlayer->crop_start_src; pPlayer->play_state = STOPPING; + pPlayer->env_state = ENV_IDLE; + DPRINTF("libzynaudioplayer: Short read (%lu) and IDLE so STOPPING\n", a_count); } } } if(pPlayer->env_state == ENV_END) pPlayer->env_state = ENV_IDLE; - if(pPlayer->play_state == STOPPING && pPlayer->env_state == ENV_IDLE) { - // Soft mute (not perfect for short last period of file but better than nowt) - + if(pPlayer->play_state == STOPPING) { + // Soft mute (not perfect for short last period of file but better than nowt). Adds a few ms of delay. for(size_t offset = 0; offset < a_count; ++offset) { pOutA[offset] *= 1.0 - ((jack_default_audio_sample_t)offset / a_count); pOutB[offset] *= 1.0 - ((jack_default_audio_sample_t)offset / a_count); } - - pPlayer->play_state = STOPPED; - pPlayer->varispeed = 0.0; - pPlayer->file_read_status = SEEKING; - //DPRINTF("libzynaudioplayer: Stopped. Used %u frames from %u in buffer to soft mute (fade). Silencing remaining %u frames (%u bytes)\n", a_count, nFrames, nFrames - a_count, (nFrames - a_count) * sizeof(jack_default_audio_sample_t)); + if(pPlayer->env_state == ENV_IDLE) { + pPlayer->play_state = STOPPED; + pPlayer->varispeed = 0.0; + pPlayer->file_read_status = SEEKING; + + // Reset MIDI triggers, e.g. held notes that are no longer valid + for(uint8_t i = 0; i < 128; ++i) + pPlayer->held_notes[i] = 0; + pPlayer->held_note = 0; + } + + DPRINTF("libzynaudioplayer: Stopped. Used %u frames from %u in buffer to soft mute (fade). Silencing remaining %u frames (%u bytes)\n", a_count, nFrames, nFrames - a_count, (nFrames - a_count) * sizeof(jack_default_audio_sample_t)); } // Silence remainder of frame memset(pOutA + a_count, 0, (nFrames - a_count) * sizeof(jack_default_audio_sample_t)); memset(pOutB + a_count, 0, (nFrames - a_count) * sizeof(jack_default_audio_sample_t)); if(pPlayer->env_state != ENV_IDLE) - for(int i = 0; i < nFrames-a_count; ++i) + for(int i = 0; i < nFrames - a_count; ++i) process_env(pPlayer); } @@ -1354,6 +1366,8 @@ int on_jack_process(jack_nframes_t nFrames, void * arg) { // Note off pPlayer->held_notes[midiEvent.buffer[1]] = 0; if(pPlayer->last_note_played == midiEvent.buffer[1]) { + if(pPlayer->loop == 3) + continue; //!@todo This is bluntly ignoring note-off but maybe we want to include envelope pPlayer->held_note = pPlayer->sustain; for (uint8_t i = 0; i < 128; ++i) { if(pPlayer->held_notes[i]) { @@ -1378,7 +1392,7 @@ int on_jack_process(jack_nframes_t nFrames, void * arg) { } if(pPlayer->held_note) continue; - if(pPlayer->sustain == 0) { + if(pPlayer->loop < 2 && pPlayer->sustain == 0) { stop_playback(pPlayer); } } @@ -1399,8 +1413,23 @@ int on_jack_process(jack_nframes_t nFrames, void * arg) { pPlayer->play_state = STARTING; } pPlayer->last_note_played = midiEvent.buffer[1]; - pPlayer->held_notes[pPlayer->last_note_played] = 1; - pPlayer->held_note = 1; + if(pPlayer->loop == 3) { + if(pPlayer->held_note) { + pPlayer->held_notes[pPlayer->last_note_played] = 0; + pPlayer->held_note = 0; + stop_playback(pPlayer); + DPRINTF("TOGGLE OFF\n"); + } else { + pPlayer->held_notes[pPlayer->last_note_played] = 1; + pPlayer->held_note = 1; + DPRINTF("TOGGLE ON\n"); + } + continue; + } + else { + pPlayer->held_notes[pPlayer->last_note_played] = 1; + pPlayer->held_note = 1; + } pPlayer->stretcher->reset(); pPlayer->varispeed = pPlayer->play_varispeed; if(!cue_point_play){ diff --git a/zynlibs/zynaudioplayer/player.h b/zynlibs/zynaudioplayer/player.h index b349ef02e..004ce7108 100644 --- a/zynlibs/zynaudioplayer/player.h +++ b/zynlibs/zynaudioplayer/player.h @@ -151,9 +151,9 @@ float get_position(AUDIO_PLAYER * pPlayer); /** @brief Set loop mode * @param player_handle Handle of player provided by init_player() -* @param bLoop True to loop at end of audio +* @param nLoop 1 to loop at end of audio, 2 to play to end (ignore MIDI note-off) */ -void enable_loop(AUDIO_PLAYER * pPlayer, uint8_t bLoop); +void enable_loop(AUDIO_PLAYER * pPlayer, uint8_t nLoop); /* @brief Get loop mode * @param player_handle Handle of player provided by init_player()