{"id":9778,"date":"2025-10-23T11:34:45","date_gmt":"2025-10-23T18:34:45","guid":{"rendered":"https:\/\/www.bodyworkfusion.com\/?page_id=9778"},"modified":"2026-01-25T17:15:52","modified_gmt":"2026-01-26T01:15:52","slug":"memories-of-the-past","status":"publish","type":"page","link":"https:\/\/www.bodyworkfusion.com\/?page_id=9778","title":{"rendered":"More Music"},"content":{"rendered":"\n<div class=\"wp-block-buttons is-content-justification-center is-layout-flex wp-container-core-buttons-is-layout-16018d1d wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/www.bodyworkfusion.com\/?page_id=1444\"><b><u><mark title=\"Make a massage appointment\" font-size=\"20px\">Book A Massage<\/mark><\/u><\/b><\/a><\/div>\n<\/div>\n\n\n    <div id=\"mx_69eb7512853ab\" class=\"musicxml-player-wrapper\">\n        \n        <div class=\"mx-selector-header\">\n            <select id=\"mx_69eb7512853ab_score-selector\" onchange=\"window.loadMusicXML('myPlayer')\">\n                <option value=\"\">Select a score<\/option><option \n        value=  \"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/CreepyDay.musicxml\" mp3_src = \"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/Creepy-Caroles.ogg\"\n\t><br \/>\n\tCreepy Caroles of the Bell<br \/>\n\t<\/option><option \n        value=  \"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/Dance-to-the-Wind.musicxml\" mp3_src = \"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/Dance-to-the-Wind.ogg\"\n\t><br \/>\n\tDance to The Wind<br \/>\n\t<\/option><option \n \t \n<option \n        value=  \"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/Eye_for_an_Eye.musicxml\" mp3_src = \"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/Eye_for_an_Eye.ogg\"]\n\t><br \/>\n\tEye for an Eye<\/p>\n<p>\t<\/option><option \n        value=  \"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/Moon_Shadow_Dance.musicxml\" mp3_src =https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/Moon_Shadow_Dance.ogg\"\n\t><br \/>\n\tMoon Shadow Dance<\/p>\n<p>\t<\/option><option \n        value=  \"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/Memories-of-the-Past-Part-1.musicxml\" mp3_src = \"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/Memories-of-the-Past-Part-1.ogg\"\n\t><br \/>\n\tMempories of the Past<br \/>\n\t<\/option><option \n        value=  \"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/Remembering-What-it-Was.musicxml\" mp3_src=\"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/Remembering-What-it-Was.ogg\"\n\t><br \/>\n\tRemeber What It Was<br \/>\n\t<\/option><option \n        value=  \"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/Anticipation.musicxml\" mp3_src=\"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/Anticipation.ogg\"\n\t><br \/>\n\tAnticipation<br \/>\n\t<\/option><option \n        value=  \"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/forgivingpianore.musicxml\" mp3_src=\"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/forgivingpiano.ogg\"\n\t><br \/>\n\tForgiven<br \/>\n\t<\/option><option \n        value=  \"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/Dreamer.musicxml\" mp3_src=\"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/10\/Dreamer.ogg\"\n\t><br \/>\n\tDreamer<br \/>\n\t<\/option><option \n        value=  \"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/11\/Misery.musicxml\" mp3_src=\"https:\/\/www.bodyworkfusion.com\/wp-content\/uploads\/2025\/11\/Misery.ogg\"\n\t><br \/>\n\tMisery<br \/>\n\t<\/option>            <\/select>\n        <\/div>\n\n        <div class=\"mx-header\">\n            <span class=\"mx-title\">More Music Collection<\/span>\n            <button class=\"mx-toggle-btn\">\n                <span class=\"open-icon\" style=\"display:none;\">\u25b2<\/span>\n                <span class=\"close-icon\">\u25bc<\/span>\n            <\/button>\n        <\/div>\n\n        <div id=\"mx_69eb7512853ab_container\" class=\"musicxml-player\">\n\n            <div class=\"mx-controls-wrapper\">\n                <div id=\"mx_69eb7512853ab_player-buttons\">\n                    <button id=\"mx_69eb7512853ab_playstop\" disabled>\u25b6 Play<\/button>\n                    <button id=\"mx_69eb7512853ab_pauseresume\" disabled>\u23f8 Pause<\/button> \n                <\/div>\n                <div id=\"mx_69eb7512853ab_jump-controls\" style=\"margin-left:10px;\">\n                    <button id=\"mx_69eb7512853ab_jump-btn\" disabled>Go to:<\/button>\n                    <input type=\"number\" id=\"mx_69eb7512853ab_measure-input\" placeholder=\"Measure #\" min=\"1\" style=\"width:90px; padding:4px;\">\n                <\/div>\n                <div id=\"mx_69eb7512853ab_tempo-controls\" style=\"margin-left:10px; display:flex; align-items:center;\">\n                    <label for=\"mx_69eb7512853ab_tempo-slider\" style=\"margin-right:5px;\">Tempo:<\/label>\n                    <input type=\"range\" id=\"mx_69eb7512853ab_tempo-slider\" min=\"50\" max=\"200\" value=\"100\" step=\"1\" style=\"width:120px;\">\n                    <span id=\"mx_69eb7512853ab_tempo-value\" style=\"margin-left:5px;\">100%<\/span>\n                <\/div>\n                <div id=\"mx_69eb7512853ab_measure-info\" style=\"font-size:1.5em;font-weight:bold;margin-bottom:1px; margin-left:5px;\">On Measure: 1<\/div> \n            <\/div>\n\n            <div id=\"mx_69eb7512853ab_loading\">Select a score to begin.<\/div>\n            <div id=\"mx_69eb7512853ab_status\"><\/div>\n            \n            <div id=\"mx_69eb7512853ab_score\" style=\"border:1px solid #ccc; margin-top:8px; padding:6px; max-height:600px; overflow:auto; position:relative;\">\n                <div id=\"mx_69eb7512853ab_sync-line\"><\/div>\n            <\/div>\n        <\/div>\n    <\/div>\n    \n    <style>\n        .musicxml-player-wrapper { \n            font-family: Arial, sans-serif; margin:15px auto; max-width:800px; \n            text-align:center; border: 1px solid #ddd; border-radius: 8px;\n        }\n        .mx-selector-header {\n            background: #e0e0e0; padding: 10px; border-radius: 8px 8px 0 0; text-align: left;\n        }\n        [id$='_score-selector'] { padding: 8px 12px; font-size: 1.1em; width: auto; min-width: 250px; }\n        .mx-header {\n            background:#f0f0f0; padding:10px; cursor: pointer; display:flex; \n            justify-content:space-between; align-items:center; font-size: 1.2em; font-weight: bold;\n        }\n        .mx-toggle-btn { background: none; border: none; font-size: 1.5em; cursor: pointer; padding: 0 5px; }\n        .mx-controls-wrapper {\n            background:#f8defa; padding:10px; display:flex; justify-content:center; \n            align-items:center; flex-wrap:wrap; position:sticky; top:0; z-index:100;\n        }\n        .musicxml-player button {\n            padding:8px 12px; margin:0 5px; border:none; border-radius:4px;\n            cursor:pointer; background-color:#4CAF50; color:white; font-size:16px;\n        }\n        .musicxml-player button:disabled { background-color:#ccc; cursor: default; }\n        .musicxml-player .mx-controls-wrapper [id$='pauseresume'] { background-color:#ff9800; }\n        .musicxml-player [id$='_score'] { border:1px solid #ccc; overflow:auto; position:relative; min-height:200px; max-height:600px; border-radius:0 0 8px 8px; }\n        .musicxml-player [id$='_loading'] { font-size:1.2em; color:#555; margin:20px auto; text-align:center; }\n        .osmd-cursor-element { display: none !important; }\n    <\/style>\n\t\n    <script src=\"https:\/\/unpkg.com\/opensheetmusicdisplay\/build\/opensheetmusicdisplay.min.js\"><\/script>\n    <script>\n    if (!window.musicxmlPlayerInstances) {\n        window.musicxmlPlayerInstances = {};\n        window.activeAudio = null; \n    }\n    const playerMap = {\"myPlayer\":\"mx_69eb7512853ab\"};\n\n    window.loadMusicXML = function(playerId) {\n        const instanceId = playerMap[playerId];\n        const player = window.musicxmlPlayerInstances[instanceId];\n        if (!player) return;\n\n        const selectedOption = player.selector.options[player.selector.selectedIndex];\n        const musicxmlSrc = selectedOption.value;\n        const mp3Src = selectedOption.getAttribute('mp3_src');\n\n        if (!musicxmlSrc) {\n            player.loadingDiv.textContent = \"Select a score to begin.\";\n            player.resetState();\n            return;\n        }\n        \n        if (window.activeAudio) {\n            window.activeAudio.pause();\n            window.activeAudio = null;\n        }\n\n        player.loadNewScore(musicxmlSrc, mp3Src);\n    };\n\n    class MusicXMLPlayer {\n        constructor(id, prefix) {\n            this.id = id;\n            this.prefix = prefix;\n            this.osmd = null;\n            this.audio = null;\n            this.measures = []; \n            this.lastDisplayedMeasure = 1;\n            this.currentTempoRate = 1.0;\n\n            \/\/ Element references\n            this.wrapper = document.getElementById(id);\n            this.container = this.wrapper.querySelector('.musicxml-player');\n            this.selector = document.getElementById(prefix + 'score-selector');\n            this.scoreDiv = document.getElementById(prefix + 'score');\n            this.measureInfoDiv = document.getElementById(prefix + 'measure-info');\n            this.tempoSlider = document.getElementById(prefix + 'tempo-slider');\n            this.tempoValue = document.getElementById(prefix + 'tempo-value');\n            this.playStopBtn = document.getElementById(prefix + 'playstop');\n            this.pauseResumeBtn = document.getElementById(prefix + 'pauseresume');\n            this.jumpBtn = document.getElementById(prefix + 'jump-btn');\n            this.measureInput = document.getElementById(prefix + 'measure-input');\n            this.loadingDiv = document.getElementById(prefix + 'loading');\n            \n            this.initListeners();\n        }\n\n        \/\/ --- NEW: Function to scroll the score div and keep cursor visible ---\n        scrollToCursor() {\n            if (!this.osmd || !this.osmd.cursor || !this.osmd.cursor.cursorElement) return;\n\n            const cursorEl = this.osmd.cursor.cursorElement;\n            const scoreDiv = this.scoreDiv;\n\n            requestAnimationFrame(() => {\n                const bbox = cursorEl.getBoundingClientRect();\n                const containerBox = scoreDiv.getBoundingClientRect();\n                \n                \/\/ Adjust horizontal scroll if cursor is out of view\n                if (bbox.left < containerBox.left || bbox.right > containerBox.right) {\n                    \/\/ Scroll to bring the cursor back, centering it somewhat\n                    scoreDiv.scrollLeft += bbox.left - containerBox.left - containerBox.width \/ 4;\n                }\n                \n                \/\/ Adjust vertical scroll if cursor is out of view (usually happens when changing systems)\n                if (bbox.top < containerBox.top || bbox.bottom > containerBox.bottom) {\n                    \/\/ Scroll to center the current system vertically\n                    scoreDiv.scrollTop += bbox.top - containerBox.top - containerBox.height \/ 2;\n                }\n            });\n        }\n        \n        \/\/ --- Core Player Methods ---\n\n        resetState() {\n            if (this.audio) {\n                this.audio.pause();\n                this.audio.currentTime = 0;\n                this.audio.removeEventListener('timeupdate', this.boundTimeUpdate);\n                this.audio.removeEventListener('ended', this.boundAudioEnded);\n            }\n            if (this.osmd) { this.osmd.clear(); this.osmd = null; }\n            \n            this.playStopBtn.textContent = \"\u25b6 Play\";\n            this.pauseResumeBtn.disabled = true;\n            this.pauseResumeBtn.textContent = \"\u23f8 Pause\";\n            this.playStopBtn.disabled = true;\n            this.jumpBtn.disabled = true;\n            this.measureInfoDiv.textContent = 'On Measure: 1';\n            this.scoreDiv.innerHTML = '';\n            this.measures = [];\n            this.lastDisplayedMeasure = 1;\n            this.loadingDiv.textContent = \"Select a score to begin.\";\n        }\n\n        async loadNewScore(musicxmlSrc, mp3Src) {\n            this.resetState();\n            this.loadingDiv.textContent = \"Please wait. Loading score...\";\n            this.loadingDiv.style.display = 'block';\n\n            try {\n                \/\/ 1. Load & Render Score\n                this.osmd = new opensheetmusicdisplay.OpenSheetMusicDisplay(this.scoreDiv, { drawCursor: true });\n                const xmlText = await (await fetch(musicxmlSrc)).text();\n                await this.osmd.load(xmlText);\n                await this.osmd.render(); \n\n                \/\/ 2. Parse Timing Data\n                const xml = new DOMParser().parseFromString(xmlText, \"application\/xml\");\n                const currentDivisions = parseInt(xml.querySelector('divisions')?.textContent || 1, 10);\n                const initialTempo = parseInt(xml.querySelector('sound[tempo]')?.getAttribute('tempo') || 120, 10);\n                let elapsedDivs = 0;\n                xml.querySelectorAll('part measure').forEach((measure, idx) => {\n                    this.measures.push({ \n                        number: idx + 1, \n                        startTime: elapsedDivs * ((60 \/ initialTempo) \/ currentDivisions) \n                    });\n                    measure.querySelectorAll('note').forEach(note => {\n                        if (!note.querySelector('chord')) elapsedDivs += parseInt(note.querySelector('duration')?.textContent || 0, 10);\n                    });\n                });\n                \n                this.loadingDiv.style.display = 'none';\n                this.playStopBtn.disabled = false;\n                this.jumpBtn.disabled = false;\n                this.jumpToMeasure(1); \/\/ Set cursor to start, triggering the initial scroll\n                this.osmd.cursor.hide(); \n\n                \/\/ 3. Audio Setup\n                if (mp3Src) {\n                    this.audio = new Audio(mp3Src);\n                    this.audio.playbackRate = this.currentTempoRate;\n                    \n                    this.boundTimeUpdate = this.handleTimeUpdate.bind(this);\n                    this.boundAudioEnded = this.handleAudioEnded.bind(this);\n\n                    this.audio.addEventListener('timeupdate', this.boundTimeUpdate);\n                    this.audio.addEventListener('ended', this.boundAudioEnded);\n                } else { \n                    this.audio = null; \n                    this.playStopBtn.disabled = true; \n                }\n\n            } catch (err) {\n                this.loadingDiv.textContent = \"Error loading score or audio. Check console for details.\";\n                this.playStopBtn.disabled = true;\n                this.jumpBtn.disabled = true;\n                console.error(\"MusicXMLPlayer Load Error:\", err);\n            }\n        }\n        \n        \/\/ --- Synchronization and Audio Handlers ---\n\n        jumpToMeasure(targetMeasure) {\n            if (!this.osmd || !this.osmd.cursor || targetMeasure < 1 || targetMeasure > this.measures.length) return;\n            this.lastDisplayedMeasure = targetMeasure;\n            this.measureInfoDiv.textContent = `On Measure: ${targetMeasure}`;\n            \n            this.osmd.cursor.reset();\n            let steps = 0;\n            while (this.osmd.cursor.iterator && this.osmd.cursor.iterator.currentMeasureIndex + 1 < targetMeasure) {\n                this.osmd.cursor.next();\n                steps++;\n                if (steps > 10000) break; \n            }\n            this.osmd.cursor.show();\n            \n            \/\/ CRITICAL: Call the scroll function after the cursor position is updated\n            this.scrollToCursor(); \n        }\n\n        handleTimeUpdate() {\n            if (!this.audio || !this.measures.length) return;\n            const adjustedTime = this.audio.currentTime * this.currentTempoRate;\n            let targetMeasure = 1;\n\n            for (let i = 0; i < this.measures.length; i++) {\n                const nextStart = this.measures[i + 1] ? this.measures[i + 1].startTime : Infinity;\n                if (adjustedTime >= this.measures[i].startTime && adjustedTime < nextStart) {\n                    targetMeasure = this.measures[i].number;\n                    break;\n                }\n            }\n            if (targetMeasure !== this.lastDisplayedMeasure) {\n                this.jumpToMeasure(targetMeasure);\n            }\n        }\n        \n        handleAudioEnded() {\n            this.playStopBtn.textContent = \"\u25b6 Play\";\n            this.pauseResumeBtn.disabled = true;\n            this.pauseResumeBtn.textContent = \"\u23f8 Pause\";\n            if (this.osmd) this.osmd.cursor.hide();\n            this.jumpToMeasure(1);\n            \n            if (window.activeAudio === this.audio) {\n                window.activeAudio = null;\n            }\n        }\n\n        \/\/ --- Event Listeners Initialization (Unchanged) ---\n        initListeners() {\n            \/\/ ... (Play\/Stop, Pause\/Resume, Tempo, Jump button logic is all here) ...\n            \n            \/\/ Play\/Stop\n            this.playStopBtn.addEventListener('click', () => {\n                if (!this.audio) return;\n                \n                if (this.audio.paused || this.audio.currentTime === 0) {\n                    if (window.activeAudio && window.activeAudio !== this.audio) {\n                        window.activeAudio.pause(); \n                    }\n                    window.activeAudio = this.audio;\n\n                    this.audio.play();\n                    this.playStopBtn.textContent = \"\u23f9 Stop\";\n                    this.pauseResumeBtn.textContent = \"\u23f8 Pause\";\n                    this.pauseResumeBtn.disabled = false;\n                    if (this.osmd) this.osmd.cursor.show();\n                } else {\n                    this.audio.pause();\n                    this.audio.currentTime = 0;\n                    this.playStopBtn.textContent = \"\u25b6 Play\";\n                    this.pauseResumeBtn.disabled = true;\n                    this.pauseResumeBtn.textContent = \"\u23f8 Pause\"; \n                    if (this.osmd) this.osmd.cursor.hide();\n                    this.jumpToMeasure(1);\n                    \n                    if (window.activeAudio === this.audio) {\n                        window.activeAudio = null;\n                    }\n                }\n            });\n            \n            \/\/ Pause\/Resume\n            this.pauseResumeBtn.addEventListener('click', () => {\n                if (!this.audio) return;\n                if (this.audio.paused) {\n                    this.audio.play();\n                    this.pauseResumeBtn.textContent = \"\u23f8 Pause\";\n                    window.activeAudio = this.audio; \n                } else {\n                    this.audio.pause();\n                    this.pauseResumeBtn.textContent = \"\u25b6 Resume\";\n                }\n            });\n            \n            \/\/ Tempo Slider\n            this.tempoSlider.addEventListener('input', () => {\n                const percent = parseInt(this.tempoSlider.value, 10);\n                this.tempoValue.textContent = `${percent}%`;\n                this.currentTempoRate = percent \/ 100;\n                if (this.audio) this.audio.playbackRate = this.currentTempoRate;\n            });\n            \n            \/\/ Jump Button (Measure Input)\n            this.jumpBtn.addEventListener('click', () => {\n                const n = parseInt(this.measureInput.value, 10);\n                if (!n || n < 1 || n > this.measures.length) {\n                    alert(`Enter a valid measure (1\u2013${this.measures.length})`);\n                    return;\n                }\n                if (this.audio && !this.audio.paused) {\n                    this.audio.pause();\n                    this.pauseResumeBtn.textContent = \"\u25b6 Resume\";\n                    if (window.activeAudio === this.audio) {\n                        window.activeAudio = null;\n                    }\n                }\n                this.jumpToMeasure(n);\n                if (this.audio) this.audio.currentTime = this.measures[n - 1].startTime;\n                this.playStopBtn.textContent = \"\u25b6 Play\";\n                this.pauseResumeBtn.disabled = true; \n                this.pauseResumeBtn.textContent = \"\u23f8 Pause\"; \n            });\n            \n            \/\/ Toggle Header logic (Unchanged)\n            const header = this.wrapper.querySelector('.mx-header');\n            const openIcon = this.wrapper.querySelector('.open-icon');\n            const closeIcon = this.wrapper.querySelector('.close-icon');\n            this.container.style.display = 'block'; \n            header.addEventListener('click', () => {\n                const isHidden = this.container.style.display === 'none';\n                this.container.style.display = isHidden ? 'block' : 'none';\n                openIcon.style.display = isHidden ? 'none' : 'inline';\n                closeIcon.style.display = isHidden ? 'inline' : 'none';\n                if (isHidden && this.osmd) this.osmd.render(); \n            });\n        }\n    }\n\n    \/\/ --- Initialization ---\n    document.addEventListener('DOMContentLoaded', () => {\n        const currentPlayerId = 'myPlayer';\n        const instanceId = playerMap[currentPlayerId];\n        \n        if (instanceId) {\n            window.musicxmlPlayerInstances[instanceId] = new MusicXMLPlayer(instanceId, instanceId + '_');\n        }\n    });\n    <\/script>\n    \n\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":8330,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-9778","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.bodyworkfusion.com\/index.php?rest_route=\/wp\/v2\/pages\/9778","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.bodyworkfusion.com\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.bodyworkfusion.com\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.bodyworkfusion.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.bodyworkfusion.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=9778"}],"version-history":[{"count":0,"href":"https:\/\/www.bodyworkfusion.com\/index.php?rest_route=\/wp\/v2\/pages\/9778\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/www.bodyworkfusion.com\/index.php?rest_route=\/wp\/v2\/pages\/8330"}],"wp:attachment":[{"href":"https:\/\/www.bodyworkfusion.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=9778"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}