ref: ba9a38b8266fd42f2bc327b7e6e60866812cbb92
dir: /src/pt2_pat2smp.c/
// for finding memory leaks in debug mode with Visual Studio #if defined _DEBUG && defined _MSC_VER #include <crtdbg.h> #endif #include <stdio.h> #include <stdint.h> #include <stdbool.h> #include <math.h> #include "pt2_config.h" #include "pt2_helpers.h" #include "pt2_visuals.h" #include "pt2_audio.h" #include "pt2_sampler.h" #include "pt2_textout.h" #include "pt2_tables.h" #include "pt2_downsample2x.h" #include "pt2_replayer.h" static const char *noteStr[12] = { "c-", "c#", "d-", "d#", "e-", "f-", "f#", "g-", "g#", "a-", "a#", "b-" }; static bool pat2SmpEndReached; static uint8_t pat2SmpFinetune = 4, pat2SmpNote = 33; // A-3 finetune +4 (default, max safe frequency) static uint8_t pat2SmpStartRow = 0, pat2SmpRows = 32; static int32_t pat2SmpPos; static double *dMixBufferL, *dMixBufferR, *dPat2SmpBuf, dPat2SmpFreq, dSeconds; static void pat2SmpOutputAudio(int32_t numSamples) { int32_t samplesTodo = numSamples; if (pat2SmpPos+samplesTodo > config.maxSampleLength) samplesTodo = config.maxSampleLength - pat2SmpPos; paulaGenerateSamples(dMixBufferL, dMixBufferR, samplesTodo*2); // 2x oversampling for (int32_t i = 0; i < samplesTodo; i++) { // 2x downsampling (decimation) double dL = decimate2x_L(dMixBufferL[(i << 1) + 0], dMixBufferL[(i << 1) + 1]); double dR = decimate2x_R(dMixBufferR[(i << 1) + 0], dMixBufferR[(i << 1) + 1]); dPat2SmpBuf[pat2SmpPos+i] = (dL + dR) * 0.5; // stereo -> mono, normalized to -128..127 later } pat2SmpPos += samplesTodo; if (pat2SmpPos >= config.maxSampleLength) pat2SmpEndReached = true; } void pat2SmpDrawNote(void) { fillRect(165, 51, FONT_CHAR_W*3, FONT_CHAR_H, video.palette[PAL_GENBKG]); textOut(165, 51, noteNames1[2+pat2SmpNote], video.palette[PAL_GENTXT]); } void pat2SmpDrawFinetune(void) { fillRect(173, 62, FONT_CHAR_W*2, FONT_CHAR_H, video.palette[PAL_GENBKG]); textOut(173, 62, ftuneStrTab[pat2SmpFinetune], video.palette[PAL_GENTXT]); } void pat2SmpDrawFrequency(void) { const int32_t maxTextWidth = 19 * FONT_CHAR_W; fillRect(164, 74, maxTextWidth, FONT_CHAR_H, video.palette[PAL_GENBKG]); if (dPat2SmpFreq*2.0 < PAL_PAULA_MAX_HZ) { textOut(164, 74, "TOO LOW!", video.palette[PAL_GENTXT]); } else { char textBuf[32]; sprintf(textBuf, "%dHz (%.1f secs)", (int32_t)(dPat2SmpFreq + 0.5), dSeconds); textOut(164, 74, textBuf, video.palette[PAL_GENTXT]); } } void pat2SmpDrawStartRow(void) { fillRect(276, 51, FONT_CHAR_W*2, FONT_CHAR_H, video.palette[PAL_GENBKG]); printTwoDecimals(276, 51, pat2SmpStartRow, video.palette[PAL_GENTXT]); } void pat2SmpDrawRows(void) { fillRect(276, 62, FONT_CHAR_W*2, FONT_CHAR_H, video.palette[PAL_GENBKG]); printTwoDecimals(276, 62, pat2SmpRows, video.palette[PAL_GENTXT]); } void pat2SmpCalculateFreq(void) { if (pat2SmpFinetune > 15) pat2SmpFinetune = 15; if (pat2SmpNote > 35) pat2SmpNote = 35; dPat2SmpFreq = PAULA_PAL_CLK / (double)periodTable[(pat2SmpFinetune * 37) + pat2SmpNote]; if (dPat2SmpFreq > PAL_PAULA_MAX_HZ) dPat2SmpFreq = PAL_PAULA_MAX_HZ; dSeconds = config.maxSampleLength / dPat2SmpFreq; pat2SmpDrawFrequency(); } void pat2SmpNoteUp(void) { if (pat2SmpNote < 35) { pat2SmpNote++; pat2SmpDrawNote(); if (pat2SmpNote == 35 && pat2SmpFinetune < 8) // high-limit to B-3 finetune 0 { pat2SmpFinetune = 0; pat2SmpDrawFinetune(); } pat2SmpCalculateFreq(); } } void pat2SmpNoteDown(void) { if (pat2SmpNote > 23) { pat2SmpNote--; pat2SmpDrawNote(); if (pat2SmpNote == 23 && pat2SmpFinetune > 7) // low-limit to B-2 finetune 0 { pat2SmpFinetune = 0; pat2SmpDrawFinetune(); } pat2SmpCalculateFreq(); } } void pat2SmpSetFinetune(uint8_t finetune) { pat2SmpFinetune = finetune & 0x0F; pat2SmpDrawFinetune(); pat2SmpCalculateFreq(); } void pat2SmpFinetuneUp(void) { if ((pat2SmpFinetune & 0xF) != 7) pat2SmpFinetune = (pat2SmpFinetune + 1) & 0xF; if (pat2SmpNote == 35 && pat2SmpFinetune < 8) // for B-3, high-limit finetune to 0 pat2SmpFinetune = 0; pat2SmpDrawFinetune(); pat2SmpCalculateFreq(); } void pat2SmpFinetuneDown(void) { if ((pat2SmpFinetune & 0xF) != 8) pat2SmpFinetune = (pat2SmpFinetune - 1) & 0xF; if (pat2SmpNote == 23 && pat2SmpFinetune > 7) // for B-2, low-limit finetune to 0 pat2SmpFinetune = 0; pat2SmpDrawFinetune(); pat2SmpCalculateFreq(); } void pat2SmpStartRowUp(void) { if (pat2SmpStartRow+pat2SmpRows < 64) { pat2SmpStartRow++; pat2SmpDrawStartRow(); } } void pat2SmpStartRowDown(void) { if (pat2SmpStartRow > 0) { pat2SmpStartRow--; pat2SmpDrawStartRow(); } } void pat2SmpRowsUp(void) { if (pat2SmpStartRow+pat2SmpRows < 64) { pat2SmpRows++; pat2SmpDrawRows(); } } void pat2SmpRowsDown(void) { if (pat2SmpRows > 1) { pat2SmpRows--; pat2SmpDrawRows(); } } void pat2SmpRender(void) { if (editor.sampleZero) { statusNotSampleZero(); return; } dPat2SmpBuf = (double *)malloc(config.maxSampleLength * sizeof (double)); if (dPat2SmpBuf == NULL) { statusOutOfMemory(); return; } const double dAudioFrequency = dPat2SmpFreq * 2.0; // *2 for oversampling int32_t maxSamplesPerTick = (int32_t)ceil(dAudioFrequency / (MIN_BPM / 2.5)) + 1; dMixBufferL = (double *)malloc(maxSamplesPerTick * sizeof (double)); dMixBufferR = (double *)malloc(maxSamplesPerTick * sizeof (double)); if (dMixBufferL == NULL || dMixBufferR == NULL) { free(dPat2SmpBuf); if (dMixBufferL != NULL) free(dMixBufferL); if (dMixBufferR != NULL) free(dMixBufferR); statusOutOfMemory(); return; } const int8_t oldRow = editor.songPlaying ? 0 : song->currRow; editor.pat2SmpOngoing = true; // this must be set first // do some prep work generateBpmTable(dPat2SmpFreq, editor.timingMode == TEMPO_MODE_VBLANK); paulaSetup(dPat2SmpFreq*2.0, MODEL_A1200); paulaDisableFilters(); storeTempVariables(); restartSong(); // this also updates BPM (samples per tick) with the PAT2SMP audio output rate clearMixerDownsamplerStates(); song->currRow = song->row = 0; pat2SmpPos = 0; uint64_t samplesToMixFrac = 0; bool lastRow = false; pat2SmpEndReached = false; while (!pat2SmpEndReached && editor.songPlaying) { /* PT replayer ticker (also sets audio.samplesPerTickInt and audio.samplesPerTickFrac). ** Returns false on end of song. */ if (!intMusic()) lastRow = true; if (song->row > pat2SmpStartRow+pat2SmpRows) break; // we rendered as many rows as requested (don't write this tick to output) uint32_t samplesToMix = audio.samplesPerTickInt; samplesToMixFrac += audio.samplesPerTickFrac; if (samplesToMixFrac >= BPM_FRAC_SCALE) { samplesToMixFrac &= BPM_FRAC_MASK; samplesToMix++; } if (lastRow && song->tick == song->speed-1) pat2SmpEndReached = true; if (lastRow || song->row > pat2SmpStartRow) pat2SmpOutputAudio(samplesToMix); } editor.pat2SmpOngoing = false; free(dMixBufferL); free(dMixBufferR); song->currRow = song->row = oldRow; // set back old row // set back audio configurations const int32_t paulaMixFrequency = audio.oversamplingFlag ? audio.outputRate*2 : audio.outputRate; paulaSetup(paulaMixFrequency, audio.amigaModel); generateBpmTable(audio.outputRate, editor.timingMode == TEMPO_MODE_VBLANK); clearMixerDownsamplerStates(); resetSong(); // this also updates BPM (samples per tick) with the tracker's audio output rate moduleSample_t *s = &song->samples[editor.currSample]; // normalize and quantize to 8-bit const double dPeak = getDoublePeak(dPat2SmpBuf, pat2SmpPos); double dAmp = INT8_MAX; if (dPeak > 0.0) dAmp = INT8_MAX / dPeak; int8_t *smpPtr = &song->sampleData[s->offset]; for (int32_t i = 0; i < pat2SmpPos; i++) { double dSmp = dPat2SmpBuf[i] * dAmp; // faster than calling round() if (dSmp < 0.0) dSmp -= 0.5; else if (dSmp > 0.0) dSmp += 0.5; int32_t smp = (int32_t)dSmp; assert(smp >= -128 && smp <= 127); // shouldn't happen according to dAmp smpPtr[i] = (int8_t)smp; } free(dPat2SmpBuf); int32_t newSampleLength = (pat2SmpPos + 1) & ~1; if (newSampleLength > config.maxSampleLength) newSampleLength = config.maxSampleLength; // clear the rest of the sample (if not full) if (newSampleLength < config.maxSampleLength) memset(&song->sampleData[s->offset+newSampleLength], 0, config.maxSampleLength - newSampleLength); // set sample attributes s->length = newSampleLength; s->volume = 64; s->fineTune = pat2SmpFinetune; s->loopStart = 0; s->loopLength = 2; // set sample name const int32_t note = pat2SmpNote % 12; const int32_t octave = (pat2SmpNote / 12) + 1; if (pat2SmpFinetune == 0) sprintf(s->text, "pat2smp(%s%d ftune: 0)", noteStr[note], octave); else if (pat2SmpFinetune < 8) sprintf(s->text, "pat2smp(%s%d ftune:+%d)", noteStr[note], octave, pat2SmpFinetune); else sprintf(s->text, "pat2smp(%s%d ftune:-%d)", noteStr[note], octave, (pat2SmpFinetune^7)-7); fixSampleBeep(s); updateCurrSample(); editor.samplePos = 0; // reset Edit Op. sample position updateWindowTitle(MOD_IS_MODIFIED); }