| 108 | } |
| 109 | |
| 110 | void Songbook::playback() { |
| 111 | if (!active() || (m_track.empty() && m_heldNotes.empty())) { |
| 112 | stop(); |
| 113 | return; |
| 114 | } |
| 115 | m_timeSourceInstance->keepalive = Time::millisecondsSinceEpoch(); |
| 116 | auto now = (Time::millisecondsSinceEpoch() - m_timeSourceInstance->epoch) / 1000.0; |
| 117 | if (!m_heldNotes.empty()) { |
| 118 | for (auto& note : m_heldNotes) |
| 119 | note.audio->setPosition(m_position); |
| 120 | eraseWhere(m_heldNotes, [&](HeldNote const& note) -> bool { |
| 121 | return note.audio->finished(); |
| 122 | }); |
| 123 | } |
| 124 | |
| 125 | while (!m_track.empty() && (m_track.first().timecode <= (now + 0.5))) { |
| 126 | auto note = m_track.takeFirst(); |
| 127 | auto delta = now - note.timecode; |
| 128 | if (delta > 1) |
| 129 | continue; // skip notes that are more than a second behind |
| 130 | if (!m_uncompressedSamples.contains(note.file)) { |
| 131 | auto sample = Root::singleton().assets()->audio(note.file); |
| 132 | if (sample->compressed()) { |
| 133 | auto copy = make_shared<Audio>(*sample); |
| 134 | copy->uncompress(); |
| 135 | m_uncompressedSamples[note.file] = copy; |
| 136 | } else |
| 137 | m_uncompressedSamples[note.file] = sample; |
| 138 | } |
| 139 | |
| 140 | AudioInstancePtr audioInstance = make_shared<AudioInstance>(*m_uncompressedSamples[note.file]); |
| 141 | audioInstance->setMixerGroup(MixerGroup::Instruments); |
| 142 | audioInstance->setPitchMultiplier(note.velocity); |
| 143 | |
| 144 | auto start = m_timeSourceInstance->epoch + (int64_t)(note.timecode * 1000.0); |
| 145 | audioInstance->setClockStart(start); |
| 146 | audioInstance->setClockStop(start + (int64_t)(note.duration * 1000.0), (int64_t)(note.fadeout * 1000.0)); |
| 147 | |
| 148 | audioInstance->setPosition(m_position); |
| 149 | |
| 150 | m_pendingAudio.append(audioInstance); |
| 151 | m_heldNotes.append(HeldNote{audioInstance, note.timecode, note.duration + note.timecode}); |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | void Songbook::render(RenderCallback* renderCallback) { |
| 156 | for (auto& a : m_pendingAudio) |
nothing calls this directly
no test coverage detected