|
From: Tapio V. <aa...@us...> - 2011-02-09 09:44:27
|
Module: editor
Branch: master
Commit: 25f8d6c6fce6a698b44d55a8692c3dea24438d9e
Author: Tapio Vierros <tap...@gm...>
Date: Wed Feb 9 10:32:29 2011 +0200
Very experimental synth mode.
Needs a lot of work, don't bother trying.
---
CMakeLists.txt | 2 +-
editor.ui | 7 +++
editorapp.cc | 8 +++-
editorapp.hh | 2 +
synth.hh | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 146 insertions(+), 2 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 819aab8..dac809b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -41,7 +41,7 @@ else(Phonon_FOUND)
endif(Phonon_FOUND)
# Headers that need MOC need to be defined separately
-file(GLOB MOC_HEADER_FILES editorapp.hh notelabel.hh notegraphwidget.hh textcodecselector.hh gettingstarted.hh pitchvis.hh)
+file(GLOB MOC_HEADER_FILES editorapp.hh notelabel.hh notegraphwidget.hh textcodecselector.hh gettingstarted.hh pitchvis.hh synth.hh)
file(GLOB SOURCE_FILES "*.cc")
file(GLOB HEADER_FILES "*.hh")
diff --git a/editor.ui b/editor.ui
index e5bbb0d..805c609 100644
--- a/editor.ui
+++ b/editor.ui
@@ -427,6 +427,13 @@
</property>
</widget>
</item>
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="chkSynth">
+ <property name="text">
+ <string>Play synthesized notes</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
<widget class="QWidget" name="tabLyrics">
diff --git a/editorapp.cc b/editorapp.cc
index d771260..8a5f6cd 100644
--- a/editorapp.cc
+++ b/editorapp.cc
@@ -26,7 +26,7 @@ namespace {
}
EditorApp::EditorApp(QWidget *parent)
- : QMainWindow(parent), gettingStarted(), noteGraph(), player(), audioOutput(), statusbarProgress(),
+ : QMainWindow(parent), gettingStarted(), noteGraph(), player(), audioOutput(), synth(), statusbarProgress(),
projectFileName(), hasUnsavedChanges(), latestPath(QDir::homePath())
{
ui.setupUi(this);
@@ -599,9 +599,15 @@ void EditorApp::playButton()
if (player && player->state() == Phonon::PlayingState) {
ui.cmdPlay->setText(tr("Pause"));
ui.cmdPlay->setIcon(QIcon::fromTheme("media-playback-pause", QIcon(":/icons/media-playback-pause.png")));
+ if (ui.chkSynth->isChecked()) {
+ synth.reset(new Synth(noteGraph->noteLabels()));
+ synth->start(player->currentTime());
+ }
} else {
ui.cmdPlay->setText(tr("Play"));
ui.cmdPlay->setIcon(QIcon::fromTheme("media-playback-start", QIcon(":/icons/media-playback-start.png")));
+ if (synth) synth->stop();
+ synth.reset();
}
}
diff --git a/editorapp.hh b/editorapp.hh
index 3c2910e..6013463 100644
--- a/editorapp.hh
+++ b/editorapp.hh
@@ -5,6 +5,7 @@
#include "ui_aboutdialog.h"
#include "operation.hh"
#include "song.hh"
+#include "synth.hh"
class QProgressBar;
class QCloseEvent;
@@ -110,6 +111,7 @@ private:
QScopedPointer<Song> song;
Phonon::MediaObject *player;
Phonon::AudioOutput *audioOutput;
+ QScopedPointer<Synth> synth;
QProgressBar *statusbarProgress;
QString projectFileName;
bool hasUnsavedChanges;
diff --git a/synth.hh b/synth.hh
new file mode 100644
index 0000000..30def9e
--- /dev/null
+++ b/synth.hh
@@ -0,0 +1,129 @@
+#pragma once
+#include <iostream>
+#include <sstream>
+#include <fstream>
+#include <string>
+#include <cmath>
+#include <QBuffer>
+#include <QFile>
+#include <phonon/MediaObject>
+#include "notes.hh"
+#include "notegraphwidget.hh"
+#include "notelabel.hh"
+
+
+#ifndef M_PI
+ #define M_PI 3.141592653589793
+#endif
+
+
+inline std::string writeWavHeader(unsigned bits, unsigned ch, unsigned sr, unsigned samples) {
+ std::ostringstream out;
+ unsigned bps = ch * bits / 8; // Bytes per sample
+ unsigned datasize = bps * samples;
+ unsigned size = datasize + 0x2C;
+ out.write("RIFF" ,4); // RIFF chunk
+ { unsigned int tmp=size-0x8 ; out.write((char*)(&tmp),4); } // RIFF chunk size
+ out.write("WAVEfmt ",8); // WAVEfmt header
+ { int tmp=0x00000010 ; out.write((char*)(&tmp),4); } // Always 0x10
+ { short tmp=0x0001 ; out.write((char*)(&tmp),2); } // Always 1
+ { short tmp = ch; out.write((char*)(&tmp),2); } // Number of channels
+ { int tmp = sr; out.write((char*)(&tmp),4); } // Sample rate
+ { int tmp = bps * sr; out.write((char*)(&tmp),4); } // Bytes per second
+ { short tmp = bps; out.write((char*)(&tmp),2); } // Bytes per frame
+ { short tmp = bits; out.write((char*)(&tmp),2); } // Bits per sample
+ out.write("data",4); // data chunk
+ { int tmp = datasize; out.write((char*)(&tmp),4); }
+ return out.str();
+}
+
+class Synth: public QObject
+{
+ Q_OBJECT
+public:
+ Synth(NoteLabels& notes) : m_notes(notes), m_phase(0), m_player(Phonon::createPlayer(Phonon::MusicCategory)), m_curBuffer()
+ {
+ m_player->setParent(this);
+ }
+
+ //~Synth() { stop(); }
+
+ /// Starts/updates the synth
+ void start(qint64 pos) {
+ calcNext(pos / 1000.0);
+ }
+
+ /// Stops the synth
+ void stop(bool killPlayback = true) {
+ killTimer(m_timerId);
+ if (killPlayback) {
+ m_player->clear();
+ m_soundData[0].close();
+ m_soundData[1].close();
+ }
+ m_phase = 0;
+ }
+
+protected:
+ /// Plays next sound
+ void timerEvent(QTimerEvent*) {
+ killTimer(m_timerId);
+ m_player->clear();
+ m_soundData[m_curBuffer].close();
+ m_soundData[m_curBuffer].setData(createBuffer());
+ m_player->setCurrentSource(&(m_soundData[m_curBuffer]));
+ m_player->play();
+ m_curBuffer = (m_curBuffer+1) % 2;
+ calcNext(m_pos);
+ }
+
+private:
+ /// Calculates the next values
+ void calcNext(double pos) {
+ NoteLabels::const_iterator it = m_notes.begin();
+ while (it != m_notes.end() && (*it)->note().begin < pos) ++it;
+ Note n = (*it)->note();
+ if (it == m_notes.end() || n.type == Note::SLEEP) { stop(false); return; }
+ m_note = n.note; // % 12;
+ qint64 nextTime = (n.begin - pos + m_length) * 1000;
+ m_length = n.length(); //((n.begin > pos) ? n.length() : (n.end - pos));
+ m_pos = n.end; // Where to start the search for next note
+ m_timerId = startTimer(nextTime > 0 ? nextTime : 1);
+ std::cout << "lyr: " << n.syllable.toStdString() << " len: " << m_length << " dela: " << (int)nextTime << std::endl;
+ }
+
+ /// Creates the sound
+ QByteArray createBuffer() {
+ // This is simple beep, so we use mono, low sample rate and only 8 bits resolution
+ // --> quick to create and small memory foot print
+ std::string header = writeWavHeader(8, 1, srate, m_length * srate);
+ QByteArray buf(header.c_str(), header.size());
+ double d = (m_note + 1) / 13.0;
+ double freq = MusicalScale().getNoteFreq(m_note + 12);
+ // Synthesize tones
+ for (size_t i = 0; i < m_length * srate; ++i) {
+ float fvalue = d * 0.2 * std::sin(m_phase) + 0.2 * std::sin(2 * m_phase) + (1.0 - d) * 0.2 * std::sin(4 * m_phase);
+ m_phase += 2.0 * M_PI * freq / srate;
+
+ // 8-bit
+ quint8 value = (fvalue + 1) * 0.5 * 255;
+ buf.push_back(value);
+ }
+
+ //std::ofstream of("/tmp/wavdump.wav");
+ //of.write(buf.data(), buf.size());
+
+ return buf;
+ }
+
+ static const int srate = 8000; ///< Sample rate
+ NoteLabels& m_notes; ///< Notes
+ double m_phase; ///< Phase
+ int m_note; ///< Next note to synthesize
+ double m_length; ///< Next note length
+ double m_pos; ///< Position from where to start looking the next note
+ Phonon::MediaObject *m_player;
+ QBuffer m_soundData[2];
+ int m_curBuffer;
+ int m_timerId; ///< Timer ID
+};
|