Browse code

Add source code of the XiPC emulator

Pawel Jablonski authored on 17/03/2023 16:46:08
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,228 @@
1
+/*
2
+ * Author: Pawel Jablonski
3
+ * E-mail: pj@xirx.net
4
+ * WWW: xirx.net
5
+ * GIT: git.xirx.net
6
+ *
7
+ * License: You can use this code however you like
8
+ * but leave information about the original author.
9
+ * Code is free for non-commercial and commercial use.
10
+ */
11
+
12
+#include "speaker.h"
13
+
14
+/**
15
+ * Constructor for the speaker class
16
+ *
17
+ * @param parent Parent object
18
+ */
19
+Speaker::Speaker(QObject *parent) : QObject(parent)
20
+{
21
+	this->audioFormat.setSampleRate(SAMPLE_RATE);
22
+	this->audioFormat.setSampleSize(16);
23
+	this->audioFormat.setChannelCount(1);
24
+	this->audioFormat.setCodec("audio/pcm");
25
+	this->audioFormat.setByteOrder(QAudioFormat::LittleEndian);
26
+	this->audioFormat.setSampleType(QAudioFormat::SignedInt);
27
+
28
+	this->audioOutput.reset(new QAudioOutput(this->audioFormat, this));
29
+	this->audioOutput->setNotifyInterval(50);
30
+
31
+	QObject::connect(this->audioOutput.data(), SIGNAL(notify()), this, SLOT(audioNotifySlot()));
32
+
33
+	this->reset();
34
+}
35
+
36
+//! Destructor for the speaker class
37
+Speaker::~Speaker()
38
+{
39
+	this->reset();
40
+
41
+	QObject::disconnect(this);
42
+}
43
+
44
+//! Reset the speaker buffers and stop playing sound
45
+void Speaker::reset()
46
+{
47
+	this->clear();
48
+}
49
+
50
+/**
51
+ * Set volume
52
+ *
53
+ * @param volume Volume level in range 0-100
54
+ */
55
+void Speaker::setVolume(unsigned int volume)
56
+{
57
+	if(volume <= 100)
58
+	{
59
+		this->audioOutput->setVolume(static_cast<qreal>(volume) / 100.0);
60
+	}
61
+}
62
+
63
+//! Clear the speaker buffers and stop playing sound
64
+void Speaker::clear()
65
+{
66
+	this->playing = false;
67
+
68
+	this->audioOutput->reset();
69
+	this->buffer.clear();
70
+
71
+	this->bufferOut.clear();
72
+	this->audioOutput->stop();
73
+
74
+	this->ioDevice = this->audioOutput->start();
75
+}
76
+
77
+/**
78
+ * Add note to the buffer
79
+ *
80
+ * @param note Note to add to the buffer
81
+ */
82
+void Speaker::addNote(const Speaker::Note &note)
83
+{
84
+	if(note.note >= NOTE_QUANTITY)
85
+	{
86
+		return;
87
+	}
88
+
89
+	if((note.time < TIME_MIN) || (note.time > TIME_MAX))
90
+	{
91
+		return;
92
+	}
93
+
94
+	if(note.note != NOTE_SILENT)
95
+	{
96
+		if((note.fill < FILL_MIN) || (note.fill > FILL_MAX))
97
+		{
98
+			return;
99
+		}
100
+
101
+		if(note.volume > VOLUME_MAX)
102
+		{
103
+			return;
104
+		}
105
+	}
106
+
107
+	Note noteItem = {};
108
+
109
+	noteItem.note = note.note;
110
+	noteItem.time = note.time;
111
+
112
+	if(note.note == NOTE_SILENT)
113
+	{
114
+		noteItem.fill = FILL_MIN;
115
+		noteItem.volume = 0;
116
+	}
117
+	else
118
+	{
119
+		noteItem.fill = (note.fill & FILL_MASK);
120
+		noteItem.volume = (note.volume & VOLUME_MASK);
121
+	}
122
+
123
+	this->buffer.append(noteItem);
124
+
125
+	if(!this->playing)
126
+	{
127
+		this->playNextNote();
128
+	}
129
+	else
130
+	{
131
+		emit updateStatusSignal(this->buffer.length());
132
+	}
133
+}
134
+
135
+/**
136
+ * Get free space size in the buffer
137
+ *
138
+ * @return Free space size in the buffer
139
+ */
140
+unsigned char Speaker::getBufferFree()
141
+{
142
+	return(static_cast<unsigned char>(BUFFER_SIZE - qMin(this->buffer.length(), BUFFER_SIZE)));
143
+}
144
+
145
+//! Load next note to play from the buffer
146
+void Speaker::playNextNote()
147
+{
148
+	if(!this->buffer.empty())
149
+	{
150
+		struct Note noteItem = this->buffer.takeFirst();
151
+
152
+		char signalLevel = static_cast<char>(VALUE_MUL * VALUE_VOLUME * (static_cast<double>(noteItem.volume) / VOLUME_MAX));
153
+
154
+		char high = ((noteItem.note == NoteCode::NOTE_SILENT) ? 0 : signalLevel);
155
+		char low = 0;
156
+
157
+		double duration = (static_cast<double>(noteItem.time) * TIME_MUL);
158
+		double fill = (static_cast<double>(noteItem.fill) / FILL_DIV);
159
+
160
+		double atomicTime = (1000.0 / SAMPLE_RATE);
161
+		double sqareTime = (1000.0 / static_cast<double>(noteFrequency[static_cast<int>(noteItem.note)]));
162
+		double time = 0.0;
163
+
164
+		QByteArray data;
165
+
166
+		for(double i = 0.0; i < (duration - 0.01); i += atomicTime)
167
+		{
168
+			data.append(static_cast<char>(0));
169
+			data.append((time < (sqareTime * fill)) ? high : low);
170
+
171
+			if(time >= sqareTime)
172
+			{
173
+				time = 0.0;
174
+			}
175
+			else
176
+			{
177
+				time += atomicTime;
178
+			}
179
+		}
180
+
181
+		this->bufferOut.append(data);
182
+
183
+		emit updateStatusSignal(this->buffer.length());
184
+
185
+		if(!this->playing)
186
+		{
187
+			this->playing = true;
188
+
189
+			this->audioNotifySlot();
190
+		}
191
+	}
192
+	else
193
+	{
194
+		this->playing = false;
195
+	}
196
+}
197
+
198
+//! Load next raw data to play to the audio output
199
+void Speaker::audioNotifySlot()
200
+{
201
+	if(this->playing)
202
+	{
203
+		int freeBytes = this->audioOutput->bytesFree();
204
+
205
+		if(freeBytes > 0)
206
+		{
207
+			int bufferBytes = this->bufferOut.length();
208
+			int bytesToWrite = qMin(bufferBytes, freeBytes);
209
+
210
+			if(bufferBytes > 0)
211
+			{
212
+				this->ioDevice->write(this->bufferOut.left(bytesToWrite));
213
+				this->bufferOut.remove(0, bytesToWrite);
214
+
215
+				if(freeBytes > bytesToWrite)
216
+				{
217
+					this->playNextNote();
218
+					this->audioNotifySlot();
219
+				}
220
+			}
221
+			else
222
+			{
223
+				this->playNextNote();
224
+				this->audioNotifySlot();
225
+			}
226
+		}
227
+	}
228
+}