From a43682962bb5dbc1d8aa6cc0c14ab2939648020e Mon Sep 17 00:00:00 2001 From: R Ferreira Date: Mon, 16 Feb 2026 23:20:34 +0000 Subject: [PATCH 1/3] Sample cursor (ntxm) --- libntxm/arm7/source/fifocommand7.cpp | 7 +++++ libntxm/arm7/source/ntxm7.cpp | 4 +++ libntxm/arm7/source/player.cpp | 42 +++++++++++++++++++++++++++- libntxm/arm9/source/fifocommand9.cpp | 11 ++++++++ libntxm/common/source/sample.cpp | 11 ++++++-- libntxm/include/ntxm/fifocommand.h | 10 ++++++- libntxm/include/ntxm/ntxm7.h | 3 ++ libntxm/include/ntxm/player.h | 6 +++- libntxm/include/ntxm/sample.h | 19 +++++++++++-- 9 files changed, 105 insertions(+), 8 deletions(-) diff --git a/libntxm/arm7/source/fifocommand7.cpp b/libntxm/arm7/source/fifocommand7.cpp index 53b96ad..da72f99 100644 --- a/libntxm/arm7/source/fifocommand7.cpp +++ b/libntxm/arm7/source/fifocommand7.cpp @@ -77,6 +77,10 @@ static void RecvCommandSetSong(SetSongCommand *c) { ntxm7->setSong((Song*)c->ptr); } +static void RecvCommandSetCursorPosPtr(SetCursorPosPtrCommand *c) { + ntxm7->setCursorPosPtr(c->cursorptr); +} + static void RecvCommandStartPlay(StartPlayCommand *c) { ntxm7->play(c->loop, c->potpos, c->row); } @@ -203,6 +207,9 @@ void CommandRecvHandler(int bytes, void *user_data) { case STOP_PLAY: RecvCommandStopPlay(&command.stopPlay); break; + case SET_CURSORPOS_PTR: + RecvCommandSetCursorPosPtr(&command.setCursorPosPtr); + break; case PLAY_INST: RecvCommandPlayInst(&command.playInst); break; diff --git a/libntxm/arm7/source/ntxm7.cpp b/libntxm/arm7/source/ntxm7.cpp index 27cf0ce..06a3f34 100644 --- a/libntxm/arm7/source/ntxm7.cpp +++ b/libntxm/arm7/source/ntxm7.cpp @@ -114,3 +114,7 @@ void NTXM7::setPatternLoop(bool loopstate) { player->setPatternLoop(loopstate); } + +void NTXM7::setCursorPosPtr(SampleCursor *cursorptr) { + player->setCursorPosPtr(cursorptr); +} \ No newline at end of file diff --git a/libntxm/arm7/source/player.cpp b/libntxm/arm7/source/player.cpp index 7d5c530..8b26015 100644 --- a/libntxm/arm7/source/player.cpp +++ b/libntxm/arm7/source/player.cpp @@ -50,7 +50,7 @@ extern bool ntxm_recording; /* ===================== PUBLIC ===================== */ Player::Player(void (*_externalTimerHandler)(void)) - :song(0), externalTimerHandler(_externalTimerHandler) + :song(0), playingNotes(0), externalTimerHandler(_externalTimerHandler) { initState(); @@ -121,6 +121,9 @@ void Player::play(u8 potpos, u16 row, bool loop) void Player::stop(void) { + for (int i = 0; i < 16; ++i) + clearPlayingData(i); + if (!state.playing) return; @@ -212,6 +215,18 @@ void Player::playNote(u8 note, u8 volume, u8 channel, u8 instidx) if (effect == EFFECT_SAMPLE_OFFSET) offs = param; + if (smp->getLoop() != 0) + offs = ntxm_clamp(offs, 0, smp->getLoopStart()); + + playingNotes[channel] = + { + .playbackpos = ((u64)((FT_OFFSET_SCALAR * offs * (smp->is16bit() ? 2 : 1)))) << 32, + .note = note, + .active = true, + .looprev = false, + .instidx = instidx, + .smpidx = inst->getNoteSample(note), + }; inst->play(note, volume, channel, offs); } @@ -249,6 +264,8 @@ void Player::stopAllNotes(u8 note, u8 instidx) // Stop playback on a channel void Player::stopChannel(u8 channel) { + clearPlayingData(channel); + // Stop single sample if it's played on this channel if((state.playing_single_sample == true) && (state.single_sample_channel == channel)) { @@ -444,6 +461,9 @@ void Player::playTimerHandler(void) chnvol = (u8)((state.channel_volume[channel]) * ((state.channel_env_vol[channel] << 8) / 0x210) / 0x1f); } + if (state.channel_env_vol[channel] < 0x01) + clearPlayingData(channel); + SCHANNEL_VOL(channel) = SOUND_VOL(chnvol); if(state.channel_active[channel] == CHANNEL_TO_BE_DISABLED) @@ -1308,6 +1328,8 @@ void Player::handleFade(u32 passed_time) // If we reached 0 ms, disable the fader (and the channel) if(state.channel_fade_ms[channel] == 0) { + clearPlayingData(channel); + state.channel_fade_active[channel] = 0; state.channel_volume[channel] = state.channel_fade_target_volume[channel]; @@ -1377,3 +1399,21 @@ bool Player::calcNextPos(u16 *nextrow, u8 *nextpotpos) // Calculate next row and return false; } + +void Player::clearPlayingData(u8 chn) +{ + playingNotes[chn] = + { + .playbackpos = 0, + .note = 0, + .active = 0, + .looprev = 0, + .instidx = 255, + .smpidx = 0, + }; +} + +void Player::setCursorPosPtr(SampleCursor *cpos) +{ + playingNotes = cpos; +} \ No newline at end of file diff --git a/libntxm/arm9/source/fifocommand9.cpp b/libntxm/arm9/source/fifocommand9.cpp index 9fbf6ef..e6c27d2 100644 --- a/libntxm/arm9/source/fifocommand9.cpp +++ b/libntxm/arm9/source/fifocommand9.cpp @@ -178,6 +178,17 @@ void CommandStopPlay(void) { fifoSendDatamsg(FIFO_NTXM, sizeof(command), (u8*)&command); } +void CommandSetCursorPosPtr(SampleCursor *cptr) +{ + NTXMFifoMessage command; + command.commandType = SET_CURSORPOS_PTR; + + SetCursorPosPtrCommand* c = &command.setCursorPosPtr; + + c->cursorptr = cptr; + + fifoSendDatamsg(FIFO_NTXM, sizeof(command), (u8*)&command); +} void CommandPlayInst(u8 inst, u8 note, u8 volume, u8 channel) { NTXMFifoMessage command; diff --git a/libntxm/common/source/sample.cpp b/libntxm/common/source/sample.cpp index ee06799..faafeaa 100644 --- a/libntxm/common/source/sample.cpp +++ b/libntxm/common/source/sample.cpp @@ -97,7 +97,8 @@ inline u32 linear_freq_table_lookup(u32 note) Sample::Sample(void *_sound_data, u32 _n_samples, u16 _sampling_frequency, bool _is_16_bit, u8 _loop, u8 _volume) :pingpong_data(0), n_samples(_n_samples), is_16_bit(_is_16_bit), loop(_loop), - loop_start(0), loop_length(0), volume(_volume), panning(128), base_panning(128) + loop_start(0), loop_length(0), volume(_volume), panning(128), base_panning(128), + sampling_frequency(_sampling_frequency) { sound_data = _sound_data; @@ -131,7 +132,7 @@ Sample::Sample(const char *filename, u8 _loop, bool *_success) sound_data = wav.getAudioData(); calcRelnoteAndFinetune( wav.getSamplingRate() ); - + sampling_frequency = wav.getSamplingRate(); u8 bit_per_sample = wav.getBitPerSample(); if(bit_per_sample == 16) @@ -302,7 +303,7 @@ void Sample::bendNoteDirect(s16 fine_step, u8 channel) u32 Sample::calcPlayLength(u8 note) { - u32 samples_per_second = LOOKUP_FREQ(48+note+rel_note,finetune); + u32 samples_per_second = getPlaybackFreq(note); if (samples_per_second == 0) return 0; return n_samples * 1000 / samples_per_second; } @@ -337,6 +338,10 @@ u32 Sample::getNSamples(void) return n_samples; } +u32 Sample::getPlaybackFreq(u8 note_) { + return LOOKUP_FREQ(48+note_+rel_note,finetune); +} + void *Sample::getData(void) { return sound_data; diff --git a/libntxm/include/ntxm/fifocommand.h b/libntxm/include/ntxm/fifocommand.h index 4962a56..6041995 100644 --- a/libntxm/include/ntxm/fifocommand.h +++ b/libntxm/include/ntxm/fifocommand.h @@ -25,6 +25,7 @@ typedef enum { DBG_OUT, UPDATE_ROW, UPDATE_POTPOS, + STOP_CURSOR, PLAY_INST, STOP_INST, STOP_MATCHING_INST, @@ -35,7 +36,8 @@ typedef enum { MIC_OFF, PATTERN_LOOP, SAMPLE_FINISH, - SET_STEREO_OUTPUT + SET_STEREO_OUTPUT, + SET_CURSORPOS_PTR } NTXMFifoMessageType; struct PlaySampleCommand @@ -86,6 +88,10 @@ struct UpdatePotPosCommand { u16 potpos; }; +struct SetCursorPosPtrCommand { + SampleCursor *cursorptr; +}; + struct PlayInstCommand { u8 inst; u8 note; @@ -144,6 +150,7 @@ typedef struct NTXMFifoMessage { StopNoteAutoCommand stopNoteAuto; PatternLoopCommand ptnLoop; SetStereoOutputCommand setStereoOutput; + SetCursorPosPtrCommand setCursorPosPtr; }; } NTXMFifoMessage; @@ -160,6 +167,7 @@ void CommandSetSong(void *song); void CommandStartPlay(u8 potpos, u16 row, bool loop); void CommandStopPlay(void); void CommandSetDebugStrPtr(char **arm7debugstrs, u16 debugstrsize, u8 n_debugbufs); +void CommandSetCursorPosPtr(SampleCursor *cursorpos); void CommandPlayInst(u8 inst, u8 note, u8 volume, u8 channel); void CommandStopInst(u8 channel); void CommandStopMatchingInst(u8 inst, u8 note); diff --git a/libntxm/include/ntxm/ntxm7.h b/libntxm/include/ntxm/ntxm7.h index 9e1d85e..520e50e 100644 --- a/libntxm/include/ntxm/ntxm7.h +++ b/libntxm/include/ntxm/ntxm7.h @@ -78,6 +78,9 @@ class NTXM7 // Set a pattern to looping void setPatternLoop(bool loopstate); + + // Set the memory address of where to write the cursor position to for arm9 + void setCursorPosPtr(SampleCursor *cursorptr); private: Player *player; diff --git a/libntxm/include/ntxm/player.h b/libntxm/include/ntxm/player.h index d2ebc71..a57d3d0 100644 --- a/libntxm/include/ntxm/player.h +++ b/libntxm/include/ntxm/player.h @@ -179,7 +179,7 @@ class Player { void playTimerHandler(void); void stopSampleFadeoutTimerHandler(void); - + void setCursorPosPtr(SampleCursor *cursorptr); private: void startPlayTimer(void); @@ -201,10 +201,14 @@ class Player { bool calcNextPos(u16 *nextrow, u8 *nextpotpos); // Calculate next row and pot position + void clearPlayingData(u8 chn); + Song *song; PlayerState state; EffectState effstate; + SampleCursor *playingNotes; + void (*externalTimerHandler)(void); void (*onRow)(u16); void (*onPatternChange)(u8); diff --git a/libntxm/include/ntxm/sample.h b/libntxm/include/ntxm/sample.h index 12e47d8..39d28c2 100644 --- a/libntxm/include/ntxm/sample.h +++ b/libntxm/include/ntxm/sample.h @@ -43,6 +43,7 @@ #define NO_VOLUME 255 + enum LoopType { NO_LOOP = 0, @@ -51,6 +52,17 @@ enum LoopType LOOP_TYPE_COUNT = 3 }; +typedef struct +{ + u64 playbackpos; + u8 note; + u8 active; + u8 looprev; + u8 instidx; + u8 smpidx; +} SampleCursor; + + #define SAMPLE_NAME_LENGTH 24 #define FT_OFFSET_SCALAR 256 @@ -78,7 +90,8 @@ class Sample u32 getSize(void); // Get the size in bytes u32 getNSamples(void); // Get the numer of (PCM) samples - + u32 getPlaybackFreq(u8 note_); + void *getData(void); u32 getMaxAmplitude(u32 startsample, u32 endsample); u32 getDynamicRange(void); @@ -117,9 +130,9 @@ class Sample // Draws a line into the sample void drawLine(int x1, int y1, int x2, int y2); //void cutSilence(void); // Heuristically cut silence in the beginning + void calcSize(void); private: - void calcSize(void); void setFormat(void); void calcRelnoteAndFinetune(u32 freq); u16 findClosestFreq(u32 freq); @@ -143,6 +156,8 @@ class Sample u8 volume; u8 panning; u8 base_panning; // xm panning effects resets when a new note is played + u32 sampling_frequency; + char name[SAMPLE_NAME_LENGTH + 1]; // These are calculated in the constructor From 460f8e0519acd4716099a79a6d9bf572159e1362 Mon Sep 17 00:00:00 2001 From: R Ferreira Date: Tue, 17 Feb 2026 19:17:37 +0000 Subject: [PATCH 2/3] Fix cursor for offset, porta, arp --- libntxm/arm7/source/player.cpp | 26 +++++++++++++++++--------- libntxm/common/source/instrument.cpp | 17 ++++++++++------- libntxm/common/source/sample.cpp | 12 ++++++++---- libntxm/include/ntxm/instrument.h | 4 ++-- libntxm/include/ntxm/sample.h | 10 +++++----- 5 files changed, 42 insertions(+), 27 deletions(-) diff --git a/libntxm/arm7/source/player.cpp b/libntxm/arm7/source/player.cpp index 8b26015..da39413 100644 --- a/libntxm/arm7/source/player.cpp +++ b/libntxm/arm7/source/player.cpp @@ -220,8 +220,8 @@ void Player::playNote(u8 note, u8 volume, u8 channel, u8 instidx) playingNotes[channel] = { - .playbackpos = ((u64)((FT_OFFSET_SCALAR * offs * (smp->is16bit() ? 2 : 1)))) << 32, - .note = note, + .playbackpos = ((u64)((FT_OFFSET_SCALAR * offs * (smp->is16bit() ? 2 : 1)))) << 31, // we also need to divide by two, so lshift 31 instead of 32 + .playbackfreq = smp->getPlaybackFreq(note), .active = true, .looprev = false, .instidx = instidx, @@ -971,22 +971,24 @@ void Player::handleTickEffects(void) if (inst == NULL) continue; + u32 newfreq = 0; switch(state.row_ticks % 3) { case(0): - inst->bendNote(state.channel_note[channel] + 0, + newfreq = inst->bendNote(state.channel_note[channel] + 0, state.channel_note[channel], 0, channel); break; case(1): - inst->bendNote(state.channel_note[channel] + halftone1, + newfreq = inst->bendNote(state.channel_note[channel] + halftone1, state.channel_note[channel], 0, channel); break; case(2): - inst->bendNote(state.channel_note[channel] + halftone2, + newfreq = inst->bendNote(state.channel_note[channel] + halftone2, state.channel_note[channel], 0, channel); break; } + if (newfreq) playingNotes[channel].playbackfreq = newfreq; break; } @@ -1000,7 +1002,9 @@ void Player::handleTickEffects(void) { state.channel_porta_accumulator[channel] = (19968 << PORTA_PRECISION); } - inst->bendNoteDirect(state.channel_note[channel], state.channel_porta_accumulator[channel] >> PORTA_PRECISION, channel); + + u32 bendfreq = inst->bendNoteDirect(state.channel_note[channel], state.channel_porta_accumulator[channel] >> PORTA_PRECISION, channel); + if (bendfreq) playingNotes[channel].playbackfreq = bendfreq; break; } @@ -1014,7 +1018,9 @@ void Player::handleTickEffects(void) { state.channel_porta_accumulator[channel] = 0; } - inst->bendNoteDirect(state.channel_note[channel], state.channel_porta_accumulator[channel] >> PORTA_PRECISION, channel); + + u32 bendfreq = inst->bendNoteDirect(state.channel_note[channel], state.channel_porta_accumulator[channel] >> PORTA_PRECISION, channel); + if (bendfreq) playingNotes[channel].playbackfreq = bendfreq; break; } @@ -1047,7 +1053,9 @@ void Player::handleTickEffects(void) { state.channel_porta_accumulator[channel] = (19968 << PORTA_PRECISION); } - inst->bendNoteDirect(state.channel_note[channel], state.channel_porta_accumulator[channel] >> PORTA_PRECISION, channel); + + u32 bendfreq = inst->bendNoteDirect(state.channel_note[channel], state.channel_porta_accumulator[channel] >> PORTA_PRECISION, channel); + if (bendfreq) playingNotes[channel].playbackfreq = bendfreq; break; } @@ -1405,7 +1413,7 @@ void Player::clearPlayingData(u8 chn) playingNotes[chn] = { .playbackpos = 0, - .note = 0, + .playbackfreq = 0, .active = 0, .looprev = 0, .instidx = 255, diff --git a/libntxm/common/source/instrument.cpp b/libntxm/common/source/instrument.cpp index 86a8598..6a4db35 100644 --- a/libntxm/common/source/instrument.cpp +++ b/libntxm/common/source/instrument.cpp @@ -161,30 +161,33 @@ void Instrument::play(u8 _note, u8 _volume, u8 _channel /* effects here */, u8 o } } -void Instrument::bendNote(u8 _note, u8 _basenote, s16 _finetune, u8 _channel) +u32 Instrument::bendNote(u8 _note, u8 _basenote, s16 _finetune, u8 _channel) { if(_note > MAX_NOTE) - return; + return NULL; switch(type) { case INST_SAMPLE: if(n_samples > 0) - samples[note_samples[_note]]->bendNote(_note, _basenote, _finetune, _channel); + return samples[note_samples[_note]]->bendNote(_note, _basenote, _finetune, _channel); break; } + + return NULL; } -void Instrument::bendNoteDirect(u8 _note, s16 _fine_step, u8 _channel) +u32 Instrument::bendNoteDirect(u8 _note, s16 _fine_step, u8 _channel) { if(_fine_step > 19968) // 19968 is the highest intermediary pitch calculation - return; // before converted to an index value for lookup table - + return NULL; // before converted to an index value for lookup table switch(type) { case INST_SAMPLE: if(n_samples > 0) - samples[note_samples[_note]]->bendNoteDirect(_fine_step, _channel); + return samples[note_samples[_note]]->bendNoteDirect(_fine_step, _channel); break; } + + return NULL; } #endif diff --git a/libntxm/common/source/sample.cpp b/libntxm/common/source/sample.cpp index faafeaa..19c0114 100644 --- a/libntxm/common/source/sample.cpp +++ b/libntxm/common/source/sample.cpp @@ -283,20 +283,24 @@ void Sample::play(u8 note, u8 volume_, u8 channel, u8 offs) SOUND_VOL(smpvolume); } -void Sample::bendNote(u8 note, u8 basenote, s16 _finetune, u8 channel) +u32 Sample::bendNote(u8 note, u8 basenote, s16 _finetune, u8 channel) { // Add 48 to the note, because otherwise absolute_note can get negative. // (The minimum value of relative note is -48) u8 absolute_note = note + 48; u8 realnote = (absolute_note+rel_note); _finetune += finetune; //Need to offset by sample's finetune - SCHANNEL_TIMER(channel) = SOUND_FREQ((int)LOOKUP_FREQ(realnote,_finetune)); + u32 bendfreq = LOOKUP_FREQ(realnote,_finetune); + SCHANNEL_TIMER(channel) = SOUND_FREQ((int)bendfreq); + return bendfreq; } -void Sample::bendNoteDirect(s16 fine_step, u8 channel) +u32 Sample::bendNoteDirect(s16 fine_step, u8 channel) { CommandDbgOut("finestep: 0x%x channel: 0x%x\n", fine_step, channel); - SCHANNEL_TIMER(channel) = SOUND_FREQ((int)GET_FREQ_DIRECT(fine_step)); + u32 bendfreq = GET_FREQ_DIRECT(fine_step); + SCHANNEL_TIMER(channel) = SOUND_FREQ((int)bendfreq); + return bendfreq; } #endif diff --git a/libntxm/include/ntxm/instrument.h b/libntxm/include/ntxm/instrument.h index 44b7245..838b1f4 100644 --- a/libntxm/include/ntxm/instrument.h +++ b/libntxm/include/ntxm/instrument.h @@ -67,8 +67,8 @@ class Instrument void setSample(u8 idx, Sample *sample); Sample *getSampleForNote(u8 _note); void play(u8 _note, u8 _volume, u8 _channel, u8 offs = 0); - void bendNote(u8 _note, u8 _basenote, s16 _finetune, u8 _channel); - void bendNoteDirect(u8 _note, s16 _fine_step, u8 _channel); + u32 bendNote(u8 _note, u8 _basenote, s16 _finetune, u8 _channel); + u32 bendNoteDirect(u8 _note, s16 _fine_step, u8 _channel); void setNoteSample(u16 note, u8 sample_id); u8 getNoteSample(u16 note); void setVolEnvEnabled(bool is_enabled); diff --git a/libntxm/include/ntxm/sample.h b/libntxm/include/ntxm/sample.h index 39d28c2..bbad395 100644 --- a/libntxm/include/ntxm/sample.h +++ b/libntxm/include/ntxm/sample.h @@ -55,11 +55,11 @@ enum LoopType typedef struct { u64 playbackpos; - u8 note; + u32 playbackfreq; u8 active; u8 looprev; u8 instidx; - u8 smpidx; + u8 smpidx; } SampleCursor; @@ -78,8 +78,8 @@ class Sample void saveAsWav(char *filename); void play(u8 note, u8 volume_, u8 channel /* effects here */, u8 offs = 0); - void bendNote(u8 note, u8 basenote, s16 _finetune, u8 channel); - void bendNoteDirect(s16 fine_step, u8 channel); + u32 bendNote(u8 note, u8 basenote, s16 _finetune, u8 channel); + u32 bendNoteDirect(s16 fine_step, u8 channel); u32 calcPlayLength(u8 note); void setRelNote(s8 _rel_note); @@ -130,9 +130,9 @@ class Sample // Draws a line into the sample void drawLine(int x1, int y1, int x2, int y2); //void cutSilence(void); // Heuristically cut silence in the beginning - void calcSize(void); private: + void calcSize(void); void setFormat(void); void calcRelnoteAndFinetune(u32 freq); u16 findClosestFreq(u32 freq); From 68197aeb6400a1724e7ece4e25a9275bfbdf3337 Mon Sep 17 00:00:00 2001 From: R Ferreira Date: Wed, 18 Feb 2026 08:50:32 +0000 Subject: [PATCH 3/3] forgot to remove STOP_CURSOR in enum --- libntxm/include/ntxm/fifocommand.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libntxm/include/ntxm/fifocommand.h b/libntxm/include/ntxm/fifocommand.h index 6041995..e22b983 100644 --- a/libntxm/include/ntxm/fifocommand.h +++ b/libntxm/include/ntxm/fifocommand.h @@ -25,7 +25,6 @@ typedef enum { DBG_OUT, UPDATE_ROW, UPDATE_POTPOS, - STOP_CURSOR, PLAY_INST, STOP_INST, STOP_MATCHING_INST,