-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathaudio_raw_stream.js
More file actions
168 lines (125 loc) · 5.12 KB
/
audio_raw_stream.js
File metadata and controls
168 lines (125 loc) · 5.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/*******************************************************************************************
*
* raylib [audio] example - Raw audio streaming
*
* This example has been created using raylib 1.6 (www.raylib.com)
* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details)
*
* Example created by Ramon Santamaria (@raysan5) and reviewed by James Hofmann (@triplefox)
*
* Copyright (c) 2015-2019 Ramon Santamaria (@raysan5) and James Hofmann (@triplefox)
* Ported to javascript 2022 by David Konsumer (@konsumer)
*
********************************************************************************************/
const r = require('../../index.js')
function memcpy (src, srcOffset, dst, dstOffset, length) {
let i
src = src.subarray || src.slice ? src : src.buffer
dst = dst.subarray || dst.slice ? dst : dst.buffer
src = srcOffset
? src.subarray
? src.subarray(srcOffset, length && srcOffset + length)
: src.slice(srcOffset, length && srcOffset + length)
: src
if (dst.set) {
dst.set(src, dstOffset)
} else {
for (i = 0; i < src.length; i++) {
dst[i + dstOffset] = src[i]
}
}
return dst
}
const MAX_SAMPLES = 512
const MAX_SAMPLES_PER_UPDATE = 4096
const screenWidth = 800
const screenHeight = 450
r.InitWindow(screenWidth, screenHeight, 'raylib [audio] example - raw audio streaming')
r.InitAudioDevice() // Initialize audio device
r.SetAudioStreamBufferSizeDefault(MAX_SAMPLES_PER_UPDATE)
// Init raw audio stream (sample rate: 22050, sample size: 16bit-short, channels: 1-mono)
const stream = r.LoadAudioStream(44100, 16, 1)
// Buffer for the single cycle waveform we are synthesizing
const data = new Uint8Array(MAX_SAMPLES)
// Frame buffer, describing the waveform when repeated over the course of a frame
const writeBuf = new Uint8Array(MAX_SAMPLES_PER_UPDATE)
let writeLength
let readLength
r.PlayAudioStream(stream) // Start processing stream buffer (no data loaded currently)
// Position read in to determine next frequency
let mousePosition = { x: -100, y: -100 }
// Cycles per second (hz)
let frequency = 440
// Previous value, used to test if sine needs to be rewritten, and to smoothly modulate frequency
let oldFrequency = 1
// Cursor to read and copy the samples of the sine wave buffer
let readCursor = 0
// Computed size in samples of the sine wave
let waveLength = 1
let oldWavelength = 1
const position = { x: 0, y: 0 }
r.SetTargetFPS(30) // Set our game to run at 30 frames-per-second
while (!r.WindowShouldClose()) {
// Update
// ----------------------------------------------------------------------------------
// Sample mouse input.
mousePosition = r.GetMousePosition()
if (r.IsMouseButtonDown(r.MOUSE_BUTTON_LEFT)) {
frequency = 40 + mousePosition.y
const pan = mousePosition.x / screenWidth
r.SetAudioStreamPan(stream, pan)
}
// Rewrite the sine wave.
// Compute two cycles to allow the buffer padding, simplifying any modulation, resampling, etc.
if (frequency !== oldFrequency) {
// Compute wavelength. Limit size in both directions.
oldWavelength = waveLength
waveLength = 22050 / frequency
if (waveLength > MAX_SAMPLES / 2) waveLength = MAX_SAMPLES / 2
if (waveLength < 1) waveLength = 1
// Write sine wave.
for (let i = 0; i < waveLength * 2; i++) {
data[i] = Math.sin((2 * Math.PI * i / waveLength) * 32000)
}
// Scale read cursor's position to minimize transition artifacts
readCursor = (readCursor * (waveLength / oldWavelength))
oldFrequency = frequency
}
// Refill audio stream if required
if (r.IsAudioStreamProcessed(stream)) {
// Synthesize a buffer that is exactly the requested size
let writeCursor = 0
while (writeCursor < MAX_SAMPLES_PER_UPDATE) {
// Start by trying to write the whole chunk at once
writeLength = MAX_SAMPLES_PER_UPDATE - writeCursor
// Limit to the maximum readable size
readLength = waveLength - readCursor
if (writeLength > readLength) writeLength = readLength
// Write the slice
memcpy(writeBuf, writeCursor, data, readCursor, writeLength)
// Update cursors and loop audio
readCursor = (readCursor + writeLength) % waveLength
writeCursor += writeLength
}
// Copy finished frame to audio stream
r.UpdateAudioStream(stream, writeBuf, MAX_SAMPLES_PER_UPDATE)
}
// ----------------------------------------------------------------------------------
// Draw
// ----------------------------------------------------------------------------------
r.BeginDrawing()
r.ClearBackground(r.RAYWHITE)
r.DrawText(`sine frequency: ${frequency}`, r.GetScreenWidth() - 220, 10, 20, r.RED)
r.DrawText('click mouse button to change frequency or pan', 10, 10, 20, r.DARKGRAY)
// Draw the current buffer state proportionate to the screen
for (let i = 0; i < screenWidth; i++) {
position.x = i
position.y = 250 + 50 * data[i * MAX_SAMPLES / screenWidth] / 32000.0
r.DrawPixelV(position, r.RED)
}
r.EndDrawing()
// ----------------------------------------------------------------------------------
}
r.UnloadAudioStream(stream)
r.CloseAudioDevice()
r.CloseWindow()