├── .gitignore ├── LICENSE ├── README.md ├── jsprep.sh ├── manifest.json ├── remote.js └── www ├── docs ├── all-videos ├── back-compat.js ├── btc ├── editor ├── gallery-basics ├── game-dev ├── index.htm ├── live ├── midi ├── omg_formats.htm ├── sequencer-surface ├── soundsets.json ├── uploading ├── vertical-surface └── what-is-this ├── img ├── beat.png ├── email.png ├── f_logo.png ├── melody.png ├── monkey48.png ├── note_eighth.png ├── notes │ ├── note_dotted_eighth.png │ ├── note_dotted_quarter.png │ ├── note_dotted_sixteenth.png │ ├── note_eighth.png │ ├── note_eighth_upside.png │ ├── note_half.png │ ├── note_no_file.png │ ├── note_quarter.png │ ├── note_rest_dotted_eighth.png │ ├── note_rest_dotted_quarter.png │ ├── note_rest_dotted_sixteenth.png │ ├── note_rest_eighth.png │ ├── note_rest_half.png │ ├── note_rest_quarter.png │ ├── note_rest_sixteenth.png │ ├── note_rest_thirtysecond.png │ ├── note_sixteenth.png │ ├── note_sixteenth_upside.png │ ├── note_thirtysecond.png │ ├── w_note_dotted_eighth.png │ ├── w_note_dotted_quarter.png │ ├── w_note_dotted_sixteenth.png │ ├── w_note_eighth.png │ ├── w_note_half.png │ ├── w_note_no_file.png │ ├── w_note_quarter.png │ ├── w_note_rest_dotted_eighth.png │ ├── w_note_rest_dotted_quarter.png │ ├── w_note_rest_dotted_sixteenth.png │ ├── w_note_rest_eighth.png │ ├── w_note_rest_half.png │ ├── w_note_rest_quarter.png │ ├── w_note_rest_sixteenth.png │ ├── w_note_rest_thirtysecond.png │ ├── w_note_sixteenth.png │ └── w_note_thirtysecond.png ├── omg.htm ├── omg_banner.png ├── overview.png ├── play-button.svg ├── section.jpg ├── section.png ├── stop-button.svg ├── technogauntlet.png └── twitter_logo.png ├── impulses ├── Sweetspot1M.wav ├── impulse_guitar.wav ├── impulse_rev.wav └── ir_rev_short.wav ├── js ├── NoteDrawer.js ├── Player.js ├── Song.js ├── fxfactory.js ├── libs │ ├── NoSleep.min.js │ ├── peakmeter.js │ ├── peakmeter_basic.js │ ├── soundfonts.json │ ├── tuna-min.js │ └── viktor │ │ ├── viktor.js │ │ └── viktor_presets.json ├── monkey.js ├── omusic-embed-draw.js ├── omusic-embed.js ├── omusic.js ├── piano_surface.js ├── remote.js ├── sequencer_surface.js ├── soundset-embed.js ├── sources.json ├── vertical_surface.js └── webmidi.js ├── piano.htm ├── playlist.htm ├── sound-resources.htm ├── soundset-editor-batch.htm ├── soundset-editor.htm ├── soundset.htm └── sources.json /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .* 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OMG Music 2 | 3 | OMG Music is a "Music Context" that wraps the web browser's AudioContext. 4 | 5 | Music created and shared in the OMG Music format contains information like key and time signatures that make the 6 | modification and mutation of beats and melodies simple. 7 | 8 | It designed for the advancement of "open music", where music is shared in a "source code" format that promotes remixing. 9 | 10 | This app supplies the core music functionality for the [OMG Platform](https://github.com/mikehelland/openmedia.gallery) 11 | 12 | ## OMG Song File Format 13 | 14 | The song and its parts are a JSON file. 15 | 16 | An overview of the file format looks like this: 17 | 18 | let song = { 19 | parts: [{name: "Piano", ...}], 20 | sections: [ 21 | { 22 | name: "Verse", 23 | parts: [{name: "Piano", ...}] 24 | } 25 | ], 26 | beatParams: {...}, 27 | keyParams: {...}, 28 | name: "My Song" 29 | } 30 | 31 | The song is separated "horizontally" into **parts** (such as piano, drums, guitar) and "vertically" into **sections** 32 | (such as Intro, Verse, Chorus). 33 | 34 | The `song.parts` array is the *part header*, which contains information about how to make sounds from the part. 35 | This may contain a list of MP3 or WAV files contained in a **Sound Set**. Or a the part might generate sounds from 36 | Web Audio API's built in oscillators, or the [WebAudioFont](https://surikov.github.io/webaudiofont/). 37 | 38 | Each section in `song.sections` also has a `parts` array. This contains information about when to play each sound, producing a melody or a beat. 39 | 40 | The `parts` in `song.sections` have either a `notes` array, or a `tracks` array. 41 | 42 | ### notes Vs tracks 43 | 44 | The `notes` array contains a list of objects that describe what note to play and for how long. Like this: 45 | 46 | notes = [ 47 | {note: 0, scaledNote: 60, beats: 2.0}, 48 | {rest: true, beats: 1.0}, 49 | {note: 1, scaledNote: 62, beats: 1.0} 50 | ] 51 | 52 | The `scaledNote` is a MIDI note determined from the key of the song and octave of the instrument 53 | of the part. In this case a middle C (MIDI note 60) is played for a two beats, then a one beat rest, 54 | then the D above it (MIDI note 62) is played for one beat. 55 | 56 | The `tracks` array can contain multiple tracks (think bass drum, snare drum, high-hat). Each track 57 | is an array that contains an element for each subbeat in the current part. If the value N is a number between 0 and 1, 58 | the audio sample for that track is played at volume N. The entire audio sample is played. 59 | 60 | Use `notes` when you need to determine how long an audio sample is played (such as in a melody or bassline). 61 | 62 | ### Classes 63 | 64 | THe main class is `OMusicContext`. This can `load()` songs or parts in the OMG format, or create blanks. 65 | 66 | The `OMGSong` and `OMGSongPart` classes can be thought of as an "exo-skeleton" for the data. For example, the following data: 67 | 68 | let songData = { 69 | parts: [{name: "Piano", ...}], 70 | sections: [ 71 | { 72 | name: "Verse", 73 | parts: [{name: "Piano", ...}] 74 | } 75 | ], 76 | beatParams: {...}, 77 | keyParams: {...}, 78 | name: "My Song" 79 | } 80 | 81 | When loaded becomes: 82 | 83 | omgSong = { 84 | data: songData, 85 | parts: { 86 | "Piano: {data: partHeaderData} 87 | } 88 | Sections: { 89 | "Verse": { 90 | data: sectionData, 91 | parts: { 92 | "Piano": {data: partDetailData} 93 | } 94 | } 95 | } 96 | } 97 | 98 | The original `songData` still exists as `omgSong.data`. The `parts` and `sections` arrays 99 | of the data have been turned into objects/dictionaries. This is for easier access to parts and sections 100 | by name, which is useful when passing data through websockets and other layers. 101 | 102 | All elements of `songData` can be accessed individually through these wrapper objects (`OMGSong` and `OMGSongPart`). 103 | The wrapper allows the player, and the context, and the user interfaces to attach temporary things which 104 | do not need to saved are cannot be stringified. For example, the GainNode for each part is on the `OMGSongPart` that 105 | wraps its the parts data. 106 | 107 | These audio nodes needs to be associated with the part, but cannot be stringified, and need to be created for each session. 108 | So they go onto the wrapper. 109 | 110 | ## Examples 111 | 112 | Here are some apps where OMG Music is used: 113 | 114 | * [OpenMedia.Gallery](https://openmedia.gallery) social networking sites 115 | * [Dawesome](https://openmedia.gallery/apps/dawesome) [(github)](https://github.com/mikehelland/dawesome) 116 | * [Techno GAUNTLET](https://openmedia.gallery/apps/gauntlet) 117 | * [OMG Meme Maker](https://openmedia.gallery/apps/meme/editor.htm) 118 | * Song Processor 119 | 120 | 121 | # Using the Client: Game Dev Example 122 | 123 | ## How to: https://www.youtube.com/watch?v=TXpPFBkpXp0 124 | 125 | (Note, the video has outdated code) 126 | 127 | ## When the game is loading: 128 | 129 | import OMusicContext from "https://openmedia.gallery/apps/music/js/omusic.js" 130 | 131 | var music = new OMusicContext() 132 | var {song, player} = await music.load("http://openmusic.gallery/data/1333") 133 | 134 | ## When the game starts: 135 | 136 | game.music.play() 137 | 138 | ## Increase BPM and key when difficulty increases: 139 | 140 | music.beatParams.bpm += 20 141 | 142 | music.keyParams.rootNote++ 143 | music.rescaleSong() 144 | 145 | ## When the game ends: 146 | 147 | music.stop() 148 | -------------------------------------------------------------------------------- /jsprep.sh: -------------------------------------------------------------------------------- 1 | cat www/js/libs/tuna-min.js www/js/fx.js www/js/omgclasses.js www/js/omusic_player.js www/js/libs/viktor/viktor.js www/js/omgservice_music.js > www/open-music-player.js 2 | 3 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "music", 3 | "activities": [ 4 | {"name": "SoundSet", "url": "soundset.htm", "views": ["SOUNDSET"]}, 5 | {"name": "SoundSet Editor", "url": "soundset-editor.htm", "edits": ["SOUNDSET"]}, 6 | {"name": "Embedded Music Viewer", "url": "js/omusic-embed.js", "className": "OMGEmbeddedViewerMusic", "embeds": ["SONG", "PART"]}, 7 | {"name": "Embedded SoundSet Viewer", "url": "js/soundset-embed.js", "className": "OMGEmbeddedViewerSOUNDSET", "embeds": ["SOUNDSET"]} 8 | ] 9 | } -------------------------------------------------------------------------------- /remote.js: -------------------------------------------------------------------------------- 1 | var remote = function (room) { 2 | 3 | return ` 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | ` 22 | }; 23 | 24 | module.exports = remote; 25 | -------------------------------------------------------------------------------- /www/docs/all-videos: -------------------------------------------------------------------------------- 1 | 28 | 29 | 30 |
31 |
32 | 33 |

OMG Overview

34 |
    35 |
  • What is OMG?
  • 36 |
  • How does it work?
  • 37 |
  • What's the point?
  • 38 |
  • What is "open music"?
  • 39 |
40 |
41 |
42 |
43 | 44 |
45 |
46 | 47 |

101: Make a Melody

48 | Learn how to make a melody using the vertical surface in OMG Create. 49 |
50 |
51 |
52 | 53 |
54 |
55 | 56 |

102: Make a Drumbeat

57 | Make your own beats using the sequencer surface in OMG Create. 58 |
59 |
60 |
61 | 62 |
63 |
64 | 65 |

Game Development

66 | Add custom, programmable, interactive music to your video game in minutes! Our most popular video! 67 |
68 |
69 |
70 | 71 |
72 |
73 | 74 |

Video Production

75 | Does your video project need music, that you don't have to pay for and won't cause you copyright headaches? 76 |
77 |
78 |
79 | 80 |
81 |
82 | 83 |

Practice with OMG

84 | Do you want to practice new drum beats? How about improvising over different scales and chord progressions on your guitar? 85 |
86 |
87 |
88 | 89 |
90 |
91 | 92 |

Make Bitcoin?!

93 | See how easy it is to make a Bitcoin tip jar that goes directly to your wallet. 94 |
95 |
96 |
97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /www/docs/back-compat.js: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | 8 | //omgsong 9 | //backwards compatibility 10 | if (!data.beatParameters) { 11 | data.beatParameters = {"beats": data.beats || 4, 12 | "subbeats": data.subbeats || 4, 13 | "measures": data.measures || 1, 14 | "shuffle": data.shuffle || 0, 15 | "subbeatMillis": data.subbeatMillis || 125 16 | }; 17 | } 18 | if (!data.keyParameters) { 19 | data.keyParameters = {"rootNote": data.rootNote || 0, 20 | "scale": data.ascale 21 | || (data.scale ? data.scale.split(",") : 0) 22 | || [0,2,4,5,7,9,11]}; 23 | } 24 | 25 | if (!data.beatParameters.bpm && data.beatParameters.subbeatMillis) { 26 | data.beatParameters.bpm = 1 / data.beatParameters.subbeatMillis * 60000 / 4; 27 | } 28 | 29 | 30 | //omgsection 31 | //backwards compatibility 32 | //with technogauntlet and omg < 0.9 33 | if (data) { 34 | if (data.beats && !data.beatParameters) { 35 | data.beatParameters = {"beats": data.beats, "subbeats": data.subbeats, 36 | "measures": data.measures || 1, "shuffle": data.shuffle || 0, 37 | "subbeatMillis": data.subbeatMillis || 125, 38 | }; 39 | } 40 | if (data.rootNote && !data.keyParameters) { 41 | data.keyParameters = {"rootNote": data.rootNote, 42 | "scale": data.ascale || data.scale.split(",")}; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /www/docs/btc: -------------------------------------------------------------------------------- 1 | 2 |

WATCH: Video Tutorial: Make Bitcoin with OMG

3 |

4 | Each thing in the gallery has a Tip Jar that accepts Bitcoin (BTC). 5 |

6 |

How to make Bitcoin on OMG:

7 |
    8 |
  1. Download a Bitcoin wallet such as Electrum
  2. 9 |
  3. Follow the setup instructions
  4. 10 |
  5. Click on "Receive Money"
  6. 11 |
  7. Copy the Bitcoin address
  8. 12 | Then: 13 |
  9. Paste the address in the btc-address on your User Page
  10. 14 | Or: 15 |
  11. Paste the address in the btc-address on the song panel in the editor
  12. 16 |
17 | -------------------------------------------------------------------------------- /www/docs/editor: -------------------------------------------------------------------------------- 1 |

The Music Editor

2 | 3 |

Also known as Gauntlet, OMG's built-in editor puts 4 | you in control of the musical puzzle pieces.

5 | 6 |

In a landscape window, the editor shows two panels, the main panel to the left 7 | and a detail panel to the right. In a portrait window (like a mobile device), 8 | the editor shows one panel at a time, along with a Back button to return to the main panel. 9 |

10 | 11 |

The Panels are:

12 | 13 | 30 | -------------------------------------------------------------------------------- /www/docs/gallery-basics: -------------------------------------------------------------------------------- 1 | 31 | 32 |

Overview: A Song is made of 33 | Sections, and Sections are made of 34 | Parts.

35 | 36 | 37 |
38 | Song 39 |
40 |
41 | Section 42 |
Part
43 |
Part
44 |
45 |
46 | Section 47 |
Part
48 |
Part
49 |
50 |
51 | Section 52 |
Part
53 |
Part
54 |
Part
55 |
56 |
57 | Section 58 |
Part
59 |
Part
60 |
Part
61 |
62 |
63 | Section 64 |
Part
65 |
Part
66 |
67 | 68 |
69 |
70 | 71 | 75 | 76 |
77 | 78 |

The gallery contains things which are stored as text files (JSON). 79 | Here are the different types of things: 80 |

81 | 92 |

93 | A Soundset contains a list of URLs to audio samples. 94 |

95 |

96 | A Part describes when and how to play the samples in a Soundset. A Part is a Drumbeat or Melody 97 |

98 |

99 | A Section contains one or more Parts to be played simultaneously. 100 |

101 |

102 | A Song contains one or more Sections to be played one-after-another. 103 |

104 |

105 | A Playlist is, of course, a list of Songs. 106 |

107 | 108 |
109 | 110 |

Things posted to the gallery can be remixed by other users. 111 | 112 |


113 | 114 | 119 | -------------------------------------------------------------------------------- /www/docs/game-dev: -------------------------------------------------------------------------------- 1 | 6 | 7 |

WATCH: Video Tutorial: Add original music to your game in minutes

8 |

9 | Adding music and sounds to your game is easy with OpenMusic.Gallery. Just add a few lines of code. 10 |

11 | 12 |

In your HTML:

13 | 14 |
15 |      <script src="https://openmusic.gallery/omg.js"></script>
16 | 
17 | 18 |

When the game is loading:

19 | 20 |
21 |      var music = new OMusicPlayer()
22 |      music.prepareSongFromURL("http://openmusic.gallery/data/1333")
23 |      var laserSound = music.getSound("SFX", "laser")
24 | 
25 | 
26 | 27 |

When the game starts:

28 | 29 |
30 |      music.play()
31 | 
32 | 33 |

When the laser is fired:

34 | 35 |
36 |      laserSound.play()
37 | 
38 | 39 |

Increase BPM and key when difficulty increases:

40 | 41 |
42 |      music.beatParams.bpm += 20
43 | 
44 |      music.keyParams.rootNote += 1
45 |      music.rescaleSong()
46 | 
47 | 48 |

When a boss character shows up:

49 | 50 |
51 |     // boss enters
52 |     music.mutePart("Boss", false);
53 |     
54 |     // boss exits
55 |     music.mutePart("Boss", true);
56 | 
57 | 58 | 59 |

When the game ends:

60 | 61 |
62 |      music.stop()
63 | 
64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /www/docs/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | OpenMusic.Gallery - Docs 10 | 11 | 12 | 13 | 42 | 43 | 44 | 45 | 46 |
47 | 51 | 52 |
53 | 54 |
55 | 56 |
57 | 58 | 59 | 77 | 78 | 79 |
80 | 81 |
82 | 83 |
84 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /www/docs/live: -------------------------------------------------------------------------------- 1 | 2 |

You can connect with other users and devices in different combinable ways.

3 | 4 |
5 | 6 |

Join Mode

7 | 8 |

In this mode, one user goes live, and other users join them. 9 | All joined devices make sound. This is for connecting users and devices in different places.

10 | 11 | 22 | 23 | Example: 24 | 29 | 30 |
31 | 32 |

Controller Mode

33 | 34 |

This is for users and devices in the same room. 35 | One device goes live, and others can act like game controllers. The controllers don't make sounds.

36 | 37 | 48 | 49 | 50 |

Example 1: Home Entertainment

51 |

You and your mates decide what to watch next. Instead, you decide to jam. Someone puts OMG Create 52 | on the TV or computer, and goes live. Everyone in the room can use their phone to control the music.

53 | 54 |

Example 2: Futuristic Instruments

55 | 56 |

You want to build a 21st century instrument like the Touchscreen Guitar or a Techno Gauntlet. 57 | Combine whatever smart devices you have around to control all aspects of music, and conduct them 58 | like a symphony.

59 | 60 |
61 | 62 |

Audience Mode

63 | 64 |

In this mode, one user goes live with a single part, and one or more users can contribute to that part. 65 | If multiple users are connected, melodies that include a note for each input are played. In a live 66 | situation this mode allows the audience to use their mobile devices to become part of the performance. 67 |

68 | 69 | 78 | 79 | -------------------------------------------------------------------------------- /www/docs/midi: -------------------------------------------------------------------------------- 1 | 2 |

The Gauntlet Editor accepts MIDI input from keyboards and drumpads.

3 | 4 |
5 | 6 |

NOTE: As of 2019 this only works on Chrome.

7 | 8 |
9 | 10 |
    11 |
  1. Connect your MIDI controller (usually a keyboard) by USB
  2. 12 |
  3. Open the OMG Create
  4. 13 |
  5. Go to the Part Options panel of the part to control
  6. 14 |
  7. Set the MIDI Channel to All or the channel 1 thru 16 of your controller
  8. 15 |
16 | 17 |

The controller will now send MIDI input to the Part, including Note On, Note Off, 18 | and changes to other wheels and knobs of the MIDI controller

19 | 20 |

See the source code for technical MIDI details.

-------------------------------------------------------------------------------- /www/docs/omg_formats.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | OpenMusic.Gallery Data Formats 8 | 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 |
25 | 26 | OpenMusic.Gallery 27 | 28 | 29 |
30 |
31 | 32 |
33 | 34 |

OMG Data Formats

35 |
36 | 37 |

Overview: A Song is made of 38 | Sections, and Sections are made of 39 | Parts.

40 | 41 | 42 |
43 | Song 44 |
45 |
46 | Section 47 |
Part
48 |
Part
49 |
50 |
51 | Section 52 |
Part
53 |
Part
54 |
55 |
56 | Section 57 |
Part
58 |
Part
59 |
Part
60 |
61 |
62 | Section 63 |
Part
64 |
Part
65 |
Part
66 |
67 |
68 | 69 | 70 |

There are two types of Parts:

71 | 72 |

73 | Part: Drumbeat 74 |

75 | 76 |

This type of Part has a tracks property which is an array of objects that 77 | can point to any available sound on the Internet by putting a URL in the sound property.

78 | 79 | 80 | 81 |

The example above sounds like this:

82 | 83 | 84 |
85 | 86 | 87 | 88 | 89 | 90 | 91 | 94 | 95 |
96 |

97 | Part: Melody/Bassline 98 |

99 | 100 |

This type of Part has a list of notes to play. 101 | The note property is not a MIDI note, but instead the note's 102 | position in the scale relative to the rootNote. This way, scale 103 | be changed, for example, from major intervals (0, 2, 4, 5, 7, 9, 11) to minor intervals 104 | (0, 2, 3, 5, 7, 8, 10) and the melody can be translated easily. 105 | Using the note, the octave, the rootNote, 106 | and the scale properties, a MIDI note can be determined. 107 |

108 | 109 | 110 | 111 |

The example above sounds like this:

112 | 113 |
114 | 115 | 118 | 119 |
120 | 121 | 122 |

Sections

123 |

A Section has a parts array that contains melodies, basslines, and drumbeats 124 | to be played at the same time.

125 | 126 | 127 | 128 |

(Here is 129 | a more detailed example)

130 | 131 |
132 | 133 | 136 | 137 | 138 |
139 | 140 | 141 |

Songs

142 |

A Song contains a list of Sections to be played one after another.

143 | 144 | 145 | 146 |

( 147 | Here is a more detailed example)

148 | 149 |
150 | 151 | 154 | 155 |
156 |

See Also:

157 |

- Source Code for OpenMusic.Gallery on GitHub

158 |

- What is open music?

159 |

- Check Out the OMG 160 | Mobile App: Techno GAUNTLET

161 | 162 |
163 |

Back to OpenMusic.Gallery home

164 | 165 | 166 |
167 | 168 | 169 | 170 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /www/docs/sequencer-surface: -------------------------------------------------------------------------------- 1 |

The sequencer surface is a drum machine for making beats.

2 | 3 |

WATCH: How to Make Beats with the Sequencer Surface

4 | 5 |

Sequencer Surface Tips

6 | 29 | -------------------------------------------------------------------------------- /www/docs/uploading: -------------------------------------------------------------------------------- 1 |

2 | OMG can use audio samples from anywhere on the Internet. You can: 3 |

4 | 5 | 10 | 11 |

Every user automatically gets 2 mb of storage on OMG.

12 | 13 |

That doesn't sound like much, but for short MP3 samples like a a snare drum, it's more than it seems.

14 | 15 | -------------------------------------------------------------------------------- /www/docs/vertical-surface: -------------------------------------------------------------------------------- 1 |

The vertical surface can be used to make to make melodies and basslines.

2 |

It's like a fretboard or keyboard, but with the out-of-key notes removed. 3 | You don't need to know anything about music to get going.

4 | 5 |

WATCH: Video Tutorial on the Vertical Surface

6 | 7 |

Vertical Surface Tips

8 | 28 | -------------------------------------------------------------------------------- /www/docs/what-is-this: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 |
25 |

What is OpenMusic.Gallery?

26 |

OpenMusic.Gallery is a platform for making and sharing open music.

27 | 28 |
29 |
30 | 31 |
32 |
33 | 34 |

What is "open music"?

35 |

Like open source software, but applied to music. Open music comes with its "source code".

36 |

You can change the tempo, the key, the levels, the FX, and the individual notes.

37 |
38 |
39 | 40 | 41 |
42 |
43 |

How does it work?

44 | 45 |

Sound files on the Internet can be arranged into drumbeats and melodies, which 46 | can be combined to make larger works of music. 47 |

48 |
49 | 50 |

You can click "Remix" on anything in the gallery to make your own version.

51 |
52 |
53 | 54 |
55 | 58 |
-------------------------------------------------------------------------------- /www/img/beat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/beat.png -------------------------------------------------------------------------------- /www/img/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/email.png -------------------------------------------------------------------------------- /www/img/f_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/f_logo.png -------------------------------------------------------------------------------- /www/img/melody.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/melody.png -------------------------------------------------------------------------------- /www/img/monkey48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/monkey48.png -------------------------------------------------------------------------------- /www/img/note_eighth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/note_eighth.png -------------------------------------------------------------------------------- /www/img/notes/note_dotted_eighth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_dotted_eighth.png -------------------------------------------------------------------------------- /www/img/notes/note_dotted_quarter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_dotted_quarter.png -------------------------------------------------------------------------------- /www/img/notes/note_dotted_sixteenth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_dotted_sixteenth.png -------------------------------------------------------------------------------- /www/img/notes/note_eighth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_eighth.png -------------------------------------------------------------------------------- /www/img/notes/note_eighth_upside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_eighth_upside.png -------------------------------------------------------------------------------- /www/img/notes/note_half.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_half.png -------------------------------------------------------------------------------- /www/img/notes/note_no_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_no_file.png -------------------------------------------------------------------------------- /www/img/notes/note_quarter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_quarter.png -------------------------------------------------------------------------------- /www/img/notes/note_rest_dotted_eighth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_rest_dotted_eighth.png -------------------------------------------------------------------------------- /www/img/notes/note_rest_dotted_quarter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_rest_dotted_quarter.png -------------------------------------------------------------------------------- /www/img/notes/note_rest_dotted_sixteenth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_rest_dotted_sixteenth.png -------------------------------------------------------------------------------- /www/img/notes/note_rest_eighth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_rest_eighth.png -------------------------------------------------------------------------------- /www/img/notes/note_rest_half.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_rest_half.png -------------------------------------------------------------------------------- /www/img/notes/note_rest_quarter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_rest_quarter.png -------------------------------------------------------------------------------- /www/img/notes/note_rest_sixteenth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_rest_sixteenth.png -------------------------------------------------------------------------------- /www/img/notes/note_rest_thirtysecond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_rest_thirtysecond.png -------------------------------------------------------------------------------- /www/img/notes/note_sixteenth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_sixteenth.png -------------------------------------------------------------------------------- /www/img/notes/note_sixteenth_upside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_sixteenth_upside.png -------------------------------------------------------------------------------- /www/img/notes/note_thirtysecond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/note_thirtysecond.png -------------------------------------------------------------------------------- /www/img/notes/w_note_dotted_eighth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/w_note_dotted_eighth.png -------------------------------------------------------------------------------- /www/img/notes/w_note_dotted_quarter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/w_note_dotted_quarter.png -------------------------------------------------------------------------------- /www/img/notes/w_note_dotted_sixteenth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/w_note_dotted_sixteenth.png -------------------------------------------------------------------------------- /www/img/notes/w_note_eighth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/w_note_eighth.png -------------------------------------------------------------------------------- /www/img/notes/w_note_half.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/w_note_half.png -------------------------------------------------------------------------------- /www/img/notes/w_note_no_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/w_note_no_file.png -------------------------------------------------------------------------------- /www/img/notes/w_note_quarter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/w_note_quarter.png -------------------------------------------------------------------------------- /www/img/notes/w_note_rest_dotted_eighth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/w_note_rest_dotted_eighth.png -------------------------------------------------------------------------------- /www/img/notes/w_note_rest_dotted_quarter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/w_note_rest_dotted_quarter.png -------------------------------------------------------------------------------- /www/img/notes/w_note_rest_dotted_sixteenth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/w_note_rest_dotted_sixteenth.png -------------------------------------------------------------------------------- /www/img/notes/w_note_rest_eighth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/w_note_rest_eighth.png -------------------------------------------------------------------------------- /www/img/notes/w_note_rest_half.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/w_note_rest_half.png -------------------------------------------------------------------------------- /www/img/notes/w_note_rest_quarter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/w_note_rest_quarter.png -------------------------------------------------------------------------------- /www/img/notes/w_note_rest_sixteenth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/w_note_rest_sixteenth.png -------------------------------------------------------------------------------- /www/img/notes/w_note_rest_thirtysecond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/w_note_rest_thirtysecond.png -------------------------------------------------------------------------------- /www/img/notes/w_note_sixteenth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/w_note_sixteenth.png -------------------------------------------------------------------------------- /www/img/notes/w_note_thirtysecond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/notes/w_note_thirtysecond.png -------------------------------------------------------------------------------- /www/img/omg.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 25 | 26 | 27 |
28 | 31 |
32 |
33 |
34 |
35 | 36 | 37 |
38 | 39 | -------------------------------------------------------------------------------- /www/img/omg_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/omg_banner.png -------------------------------------------------------------------------------- /www/img/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/overview.png -------------------------------------------------------------------------------- /www/img/play-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 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 | -------------------------------------------------------------------------------- /www/img/section.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/section.jpg -------------------------------------------------------------------------------- /www/img/section.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/section.png -------------------------------------------------------------------------------- /www/img/stop-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 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 | -------------------------------------------------------------------------------- /www/img/technogauntlet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/technogauntlet.png -------------------------------------------------------------------------------- /www/img/twitter_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/img/twitter_logo.png -------------------------------------------------------------------------------- /www/impulses/Sweetspot1M.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/impulses/Sweetspot1M.wav -------------------------------------------------------------------------------- /www/impulses/impulse_guitar.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/impulses/impulse_guitar.wav -------------------------------------------------------------------------------- /www/impulses/impulse_rev.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/impulses/impulse_rev.wav -------------------------------------------------------------------------------- /www/impulses/ir_rev_short.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehelland/omg-music/931bcbf485f04dbcac612d25a3a6221768dc6cb2/www/impulses/ir_rev_short.wav -------------------------------------------------------------------------------- /www/js/NoteDrawer.js: -------------------------------------------------------------------------------- 1 | export default function NoteDrawer() { 2 | 3 | this.useUnicodeNotes = navigator.userAgent.indexOf("Android") === -1 4 | && navigator.userAgent.indexOf("iPhone") === -1 5 | && navigator.userAgent.indexOf("iPad") === -1 6 | && navigator.userAgent.indexOf("Mac OS X") === -1 ; 7 | 8 | //this.useUnicodeNotes = false 9 | if (!this.useUnicodeNotes) { 10 | this.setupNoteImages(); 11 | } 12 | 13 | } 14 | NoteDrawer.prototype.onReadyListeners = [] 15 | 16 | NoteDrawer.prototype.onReady = function (callback) { 17 | if (this.useUnicodeNotes || this.loaded) { 18 | callback() 19 | return 20 | } 21 | 22 | NoteDrawer.prototype.onReadyListeners.push(callback) 23 | } 24 | 25 | NoteDrawer.prototype.setupNoteImages = function () { 26 | if (NoteDrawer.prototype.noteImages) 27 | return; 28 | 29 | this.noteImageUrls = [ 30 | [2, "note_half", "note_rest_half"], 31 | [1.5, "note_dotted_quarter", "note_rest_dotted_quarter"], 32 | [1, "note_quarter", "note_rest_quarter"], 33 | [0.75, "note_dotted_eighth", "note_rest_dotted_eighth"], 34 | [0.5, "note_eighth", "note_rest_eighth"], //, "note_eighth_upside"], 35 | [0.375, "note_dotted_sixteenth", "note_rest_dotted_sixteenth"], 36 | [0.25, "note_sixteenth", "note_rest_sixteenth"], //, "note_sixteenth_upside"], 37 | [0.125, "note_thirtysecond", "note_rest_thirtysecond"], 38 | [-1, "note_no_file", "note_no_file"] 39 | ]; 40 | 41 | var musicUrl = "/apps/music/"; 42 | var noteImages = []; 43 | var loadedNotes = 0; 44 | var areAllNotesLoaded = () => { 45 | loadedNotes++; 46 | if (loadedNotes == this.noteImageUrls.length * 4) { 47 | this.loaded = true 48 | for (var listener of NoteDrawer.prototype.onReadyListeners) { 49 | listener() 50 | } 51 | NoteDrawer.prototype.onReadyListeners = [] 52 | } 53 | }; 54 | NoteDrawer.prototype.noteImages = noteImages; 55 | 56 | for (var i = 0; i < this.noteImageUrls.length; i++) { 57 | 58 | var noteImage = new Image(); 59 | noteImage.onload = areAllNotesLoaded; 60 | noteImage.src = musicUrl + this.getNoteImageUrl(i, 1); 61 | 62 | var noteWhiteImage = new Image(); 63 | noteWhiteImage.onload = areAllNotesLoaded; 64 | noteWhiteImage.src = musicUrl + this.getNoteImageUrl(i, 1, true); 65 | 66 | var restImage = new Image(); 67 | restImage.onload = areAllNotesLoaded; 68 | restImage.src = musicUrl + this.getNoteImageUrl(i, 2); 69 | 70 | var restWhiteImage = new Image(); 71 | restWhiteImage.onload = areAllNotesLoaded; 72 | restWhiteImage.src = musicUrl + this.getNoteImageUrl(i, 2, true); 73 | 74 | var imageBundle = [noteImage, noteWhiteImage, restImage, restWhiteImage]; 75 | var upsideDown = this.getNoteImageUrl(i, 3); 76 | if (upsideDown) { 77 | var upsideImage = new Image(); 78 | upsideImage.src = musicUrl + upsideDown; 79 | imageBundle.push(upsideImage); 80 | } 81 | 82 | noteImages.push(imageBundle); 83 | } 84 | }; 85 | 86 | 87 | NoteDrawer.prototype.getImageForNote = function (note, highContrast) { 88 | 89 | var index = (note.rest ? 2 : 0) + (highContrast ? 1 : 0) 90 | var draw_noteImage = this.noteImages[8][index]; 91 | if (note.beats == 2.0) { 92 | draw_noteImage = this.noteImages[0][index]; 93 | } 94 | if (note.beats == 1.5) { 95 | draw_noteImage = this.noteImages[1][index]; 96 | } 97 | if (note.beats == 1.0) { 98 | draw_noteImage = this.noteImages[2][index]; 99 | } 100 | if (note.beats == 0.75) { 101 | draw_noteImage = this.noteImages[3][index]; 102 | } 103 | if (note.beats == 0.5) { 104 | draw_noteImage = this.noteImages[4][index]; 105 | } 106 | if (note.beats == 0.375) { 107 | draw_noteImage = this.noteImages[5][index]; 108 | } 109 | if (note.beats == 0.25) { 110 | draw_noteImage = this.noteImages[6][index]; 111 | } 112 | if (note.beats == 0.125) { 113 | draw_noteImage = this.noteImages[7][index]; 114 | } 115 | 116 | return draw_noteImage; 117 | 118 | }; 119 | 120 | NoteDrawer.prototype.getNoteImageUrl = function (i, j, highContrast) { 121 | var fileName = this.noteImageUrls[i][j]; 122 | if (fileName) { 123 | return "img/notes/" + (highContrast ? "w_" : "") + fileName + ".png"; 124 | } 125 | }; 126 | 127 | 128 | NoteDrawer.prototype.getTextForNote = function (note, highContrast) { 129 | 130 | switch (note.beats) { 131 | case 4.0: return note.rest ? "\u{1D13B}" : "\u{1D15D}"; 132 | case 3.75: return note.rest ? "\u{1D13C} \u{1D13D} \u{1D13E} \u{1D13F}" : "\u{1D15E} \u{1D16D} \u{1D16D} \u{1D16D}"; 133 | case 3.5: return note.rest ? "\u{1D13C} \u{1D13D} \u{1D13E}" : "\u{1D15E} \u{1D16D} \u{1D16D}"; 134 | case 3.25: return note.rest ? "\u{1D13C} \u{1D13D} \u{1D13F}" : "\u{1D15E} \u{1D16D} \u{1D161}"; 135 | case 3.0: return note.rest ? "\u{1D13C} \u{1D13D}" : "\u{1D15E} \u{1D16D}"; 136 | case 2.75: return note.rest ? "\u{1D13C} \u{1D13E} \u{1D13F}" : "\u{1D15E} \u{1D160} \u{1D161}"; 137 | case 2.5: return note.rest ? "\u{1D13C} \u{1D13E}" : "\u{1D15E} \u{1D160}"; 138 | case 2.25: return note.rest ? "\u{1D13C} \u{1D13F}" : "\u{1D15E} \u{1D161}"; 139 | case 2.0: return note.rest ? "\u{1D13C}" : "\u{1D15E}"; 140 | case 1.75: return note.rest ? "\u{1D13D} \u{1D13E} \u{1D13F}" : "\u{1D15F} \u{1D16D} \u{1D16D}"; 141 | case 1.5: return note.rest ? "\u{1D13D} \u{1D13E}" : "\u{1D15F} \u{1D16D}"; 142 | case 1.25: return note.rest ? "\u{1D13D} \u{1D13F}" : "\u{1D15F} \u{1D161}"; 143 | case 1.0: return note.rest ? "\u{1D13D}" : "\u{1D15F}"; 144 | case 0.75: return note.rest ? "\u{1D13E} \u{1D13F}" : "\u{1D160} \u{1D16D}"; 145 | case 0.5: return note.rest ? "\u{1D13E}" : "\u{1D160}"; 146 | case 0.375: return note.rest ? "\u{1D13F} \u{1D140}" : "\u{1D161} \u{1D16D}"; 147 | case 0.25: return note.rest ? "\u{1D13F}" : "\u{1D161}"; 148 | case 0.125: return note.rest ? "\u{1D140}" : "\u{1D162}"; 149 | } 150 | 151 | return note.beats > 4 ? (note.rest ? "\u{1D13B} \u{1D144}" : "\u{1D15D} \u{1D144}") : "?"; 152 | }; 153 | 154 | NoteDrawer.prototype.noteSize = 30 155 | NoteDrawer.prototype.drawNote = function (note, ctx, x, y) { 156 | 157 | ctx.font = this.noteSize + "pt serif"; 158 | 159 | // todo don't hard code this value 160 | if (y < 30) { 161 | ctx.save() 162 | ctx.translate(x, y) 163 | ctx.scale(1, -1) 164 | x = 0 165 | y = 0 166 | } 167 | 168 | if (this.useUnicodeNotes) { 169 | ctx.fillText(this.getTextForNote(note), x, y); 170 | } 171 | else { 172 | ctx.drawImage(this.getImageForNote(note, this.highContrast), 173 | x, y - this.noteSize, this.noteSize, this.noteSize); 174 | } 175 | 176 | if (y < 30) { 177 | ctx.restore() 178 | } 179 | 180 | } 181 | 182 | // this forces the images to load into the prototype 183 | let nd = new NoteDrawer() 184 | 185 | /*this.splitInts = function (string) { 186 | var ints = string.split(","); 187 | for (var i = 0; i < ints.length; i++) { 188 | ints[i] = parseInt(ints[i]); 189 | } 190 | return ints; 191 | }; 192 | 193 | NoteDrawer.prototype.getChordText = function (chord, ascale) { 194 | while (chord < 0) { 195 | chord += ascale.length; 196 | } 197 | while (chord >= ascale.length) { 198 | chord -= ascale.length; 199 | } 200 | var chordInterval = ascale[chord]; 201 | if (chordInterval === 0) { 202 | return "I"; 203 | } 204 | else if (chordInterval === 2) return "II"; 205 | else if (chordInterval === 3 || chordInterval === 4) return "III"; 206 | else if (chordInterval === 5) return "IV"; 207 | else if (chordInterval === 6) return "Vb"; 208 | else if (chordInterval === 7) return "V"; 209 | else if (chordInterval === 8 || chordInterval === 9) return "VI"; 210 | else if (chordInterval === 10 || chordInterval === 11) return "VII"; 211 | return "?"; 212 | } 213 | 214 | NoteDrawer.prototype.getChordProgressionText = function (section) { 215 | var chordsText = ""; 216 | if (section.data.chordProgression) { 217 | var chords = section.data.chordProgression; 218 | for (var i = 0; i < chords.length; i++) { 219 | if (i > 0) { 220 | chordsText += " - "; 221 | } 222 | chordsText += this.getChordText(chords[i], section.data.ascale); 223 | } 224 | } 225 | return chordsText; 226 | };*/ 227 | 228 | 229 | 230 | 231 | -------------------------------------------------------------------------------- /www/js/Song.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | console.log("Song.js") 4 | // OMGSong wraps an OMG song so things like the player and DAW needs to can be added 5 | // it's for storing anything related to session that can't be stringified 6 | 7 | export default function OMGSong(musicContext, data) { 8 | 9 | this.musicContext = musicContext 10 | 11 | // each song has sections (eg intro, verse, chorus) and parts (eg drums, bass, keyboard) 12 | // each section also has parts 13 | // song.parts describes the part's soundset and fx and audio nodes 14 | // song.section.parts describes the part's data (what notes to play when) 15 | 16 | 17 | this.parts = {} 18 | this.sections = {} 19 | this.fx = [] 20 | this.loop = true; 21 | this.gain = 1 22 | 23 | this.data = data || {} 24 | this.data.type = this.data.type || "SONG" 25 | this.data.name = this.data.name || "" 26 | this.saved = this.data.id ? true : false 27 | 28 | if (!this.data.parts) { 29 | this.backwardsCompat1 = true 30 | this.data.parts = [] 31 | } 32 | if (!this.data.sections) { 33 | this.data.sections = [] 34 | } 35 | 36 | // the song also has an arrangement, an array of which sections to play 37 | 38 | this.arrangement = [] 39 | /* if (!this.data.arrangement) { 40 | this.data.arrangement = [] 41 | } */ 42 | 43 | // todo come up with a version scheme 44 | //this.data.omgVersion = Math.max(1, (this.omgVersion || 0)); 45 | 46 | if (!this.data.fx) { 47 | this.data.fx = []; 48 | } 49 | 50 | if (!this.data.beatParams) { 51 | this.data.beatParams = {}; 52 | } 53 | this.data.beatParams.subbeats = this.data.beatParams.subbeats * 1 || 4; 54 | this.data.beatParams.beats = this.data.beatParams.beats * 1 || 4; 55 | this.data.beatParams.measures = this.data.beatParams.measures * 1 || 1; 56 | this.data.beatParams.bpm = this.data.beatParams.bpm * 1 || 120; 57 | this.data.beatParams.shuffle = this.data.beatParams.shuffle * 1 || 0; 58 | if (!this.data.keyParams) { 59 | this.data.keyParams = {}; 60 | } 61 | this.data.keyParams.rootNote = this.data.keyParams.rootNote * 1 || 0; 62 | this.data.keyParams.scale = this.data.keyParams.scale || [0,2,4,5,7,9,11]; 63 | 64 | this.onKeyChangeListeners = []; 65 | this.onBeatChangeListeners = []; 66 | this.onPartAudioParamsChangeListeners = []; 67 | this.onPartAddListeners = []; 68 | this.onChordProgressionChangeListeners = []; 69 | this.onFXChangeListeners = []; 70 | this.onPartChangeListeners = []; 71 | 72 | this.loadParts() 73 | this.loadSections() 74 | }; 75 | 76 | 77 | OMGSong.prototype.loadParts = function () { 78 | for (var part of this.data.parts) { 79 | this.parts[part.name] = new OMGSongPart(part) 80 | this.parts[part.name].song = this 81 | } 82 | 83 | if (!this.backwardsCompat1) { 84 | //return 85 | } 86 | 87 | // find any parts that might not be in the header 88 | // mainly for backwards compat, <2021 89 | //get rid of it 90 | /*for (var section of this.data.sections) { 91 | if (section.parts) { 92 | for (part of section.parts) { 93 | if (!this.parts[part.name]) { 94 | console.log("lateload", part.name) 95 | var data = JSON.parse(JSON.stringify(part)) 96 | delete data.notes 97 | delete data.tracks 98 | this.data.parts.push(data) 99 | var omgpart = new OMGSongPart(data) 100 | this.parts[omgpart.data.name] = omgpart 101 | this.parts[omgpart.data.name].song = this 102 | } 103 | } 104 | } 105 | }*/ 106 | 107 | } 108 | 109 | OMGSong.prototype.loadSections = function () { 110 | var addToArrangement 111 | if (!this.data.arrangement) { 112 | this.arrangement = [] 113 | addToArrangement = true 114 | } 115 | else { 116 | this.arrangement = this.data.arrangement 117 | } 118 | for (var sectionData of this.data.sections) { 119 | var section = this.makeSection(sectionData) 120 | this.sections[sectionData.name] = section 121 | this.loadSectionParts(section) 122 | 123 | if (addToArrangement) { 124 | this.arrangement.push({section, repeats: 1}) 125 | } 126 | } 127 | } 128 | 129 | OMGSong.prototype.loadSectionParts = function (section) { 130 | for (var partData of section.data.parts) { 131 | var part = new OMGSongPart(partData, this.parts[partData.name]) 132 | part.song = this 133 | part.section = section 134 | section.parts[partData.name] = part 135 | } 136 | } 137 | 138 | OMGSong.prototype.addPart = function (data, source) { 139 | var headPart = new OMGSongPart(data) 140 | var name = this.getUniqueName(headPart.data.name, this.parts) 141 | headPart.data.name = name 142 | this.parts[name] = headPart 143 | this.parts[name].song = this 144 | this.data.parts[name] = headPart.data 145 | this.musicContext.loadPartHeader(headPart) 146 | 147 | for (var sectionName in this.sections) { 148 | var section = this.sections[sectionName] 149 | let part = new OMGSongPart({}, headPart) //JSON.parse(JSON.stringify(headPart.data)) 150 | part.section = section 151 | part.song = this 152 | section.parts[headPart.data.name] = part 153 | section.data.parts.push(part.data) 154 | this.musicContext.loadPart(part) 155 | } 156 | 157 | this.onPartAddListeners.forEach(listener => listener(headPart, source)); 158 | return headPart 159 | } 160 | 161 | OMGSong.prototype.addSection = function (copy) { 162 | var name = (copy && copy.name) ? copy.name : "Section 1" 163 | name = this.getUniqueName(name, this.sections) 164 | if (copy) { 165 | copy = JSON.parse(JSON.stringify(copy)) 166 | copy.name = name 167 | } 168 | var section = this.makeSection(copy) 169 | this.sections[name] = section 170 | this.data.sections[name] = section.data 171 | 172 | this.loadSectionParts(section) 173 | this.musicContext.loadSection(section) 174 | 175 | var arrangementSection = {section, repeats: 1} 176 | this.arrangement.push(arrangementSection) 177 | return arrangementSection 178 | } 179 | 180 | OMGSong.prototype.makeSection = function (data) { 181 | data = data || {} 182 | data.name = data.name || "Section" 183 | data.parts = data.parts || [] 184 | data.measures = data.measures || 1 185 | data.chordProgression = data.chordProgression|| [0] 186 | return {data, parts: {}, song: this} 187 | } 188 | 189 | OMGSong.prototype.keyChanged = function (source) { 190 | this.onKeyChangeListeners.forEach(listener => listener(this.data.keyParams, source)); 191 | }; 192 | 193 | OMGSong.prototype.beatsChanged = function (source) { 194 | this.onBeatChangeListeners.forEach(listener => listener(this.data.beatParams, source)); 195 | }; 196 | 197 | OMGSong.prototype.partMuteChanged = function (part, source) { 198 | this.onPartAudioParamsChangeListeners.forEach(listener => listener(part, source)); 199 | }; 200 | 201 | OMGSong.prototype.chordProgressionChanged = function (source) { 202 | this.onChordProgressionChangeListeners.forEach(listener => listener(source)); 203 | }; 204 | 205 | OMGSong.prototype.fxChanged = function (action, part, fx, source) { 206 | this.onFXChangeListeners.forEach(listener => listener(action, part, fx, source)); 207 | }; 208 | 209 | 210 | OMGSong.prototype.partChanged = function (part, track, subbeat, value, source) { 211 | if (this.onPartChangeListeners.length === 0) { 212 | return 213 | } 214 | for (var listener of this.onPartChangeListeners) { 215 | listener(part, track, subbeat, value, source) 216 | } 217 | }; 218 | 219 | 220 | 221 | 222 | OMGSong.prototype.getData = function () { 223 | 224 | var data = this.data 225 | 226 | data.parts = [] 227 | data.sections = []; 228 | 229 | for (var sectionName in this.sections) { 230 | var section = this.sections[sectionName] 231 | section.data.parts = [] 232 | for (var part in section.parts) { 233 | section.data.parts.push(section.parts[part].data) 234 | } 235 | 236 | data.sections.push(section.data) 237 | } 238 | 239 | for (var part in this.parts) { 240 | data.parts.push(this.parts[part].data) 241 | } 242 | 243 | return JSON.parse(JSON.stringify(data)); 244 | }; 245 | 246 | 247 | OMGSong.prototype.rescale = function () { 248 | 249 | for (var i = 0; i < this.sections.length; i++) { 250 | this.sections[i].rescale() 251 | } 252 | 253 | } 254 | 255 | OMGSong.prototype.setKey = function (rootNote, scale, source) { 256 | this.data.keyParams.scale = scale; 257 | this.data.keyParams.rootNote = rootNote; 258 | this.keyChanged(source); 259 | } 260 | 261 | OMGSong.prototype.getUniqueName = function (name, names) { 262 | 263 | if (!names[name]) { 264 | return name 265 | } 266 | 267 | var ending; 268 | var i = name.lastIndexOf(" "); 269 | if (i > -1 && i < name.length) { 270 | ending = name.substr(i + 1); 271 | if (!isNaN(ending * 1)) { 272 | return this.getUniqueName(name.substr(0, i + 1) + (ending * 1 + 1), names); 273 | } 274 | } 275 | return this.getUniqueName(name + " 2", names); 276 | }; 277 | 278 | 279 | OMGSong.prototype.removeSection = function (arrangementSection) { 280 | var uses = 0 281 | for (var i = this.arrangement.length - 1; i >= 0; i--) { 282 | if (this.arrangement[i].section === arrangementSection.section) { 283 | uses++ 284 | } 285 | } 286 | 287 | i = this.arrangement.indexOf(arrangementSection) 288 | if (i > -1) { 289 | this.arrangement.splice(i, 1) 290 | } 291 | 292 | // if we removed the section's only use, remove it from sections 293 | if (uses === 1) { 294 | delete this.sections[arrangementSection.section.data.name] 295 | } 296 | 297 | // todo fire change listener 298 | 299 | } 300 | 301 | OMGSong.prototype.removePart = function (part) { 302 | 303 | for (var s in this.sections) { 304 | for (var p in this.sections[s].parts) { 305 | if (p === part.data.name) { 306 | delete this.sections[s].parts[p] 307 | break 308 | } 309 | } 310 | for (var i = 0; i < this.sections[s].data.parts.length; i++) { 311 | if (this.sections[s].data.parts[i].name === part.data.name) { 312 | this.sections[s].data.parts.splice(i, 1) 313 | break 314 | } 315 | } 316 | 317 | } 318 | 319 | delete this.parts[part.data.name] 320 | 321 | } 322 | 323 | OMGSong.prototype.getFX = function (name) { 324 | for (var ip = 0; ip < this.fx.length; ip++) { 325 | if (this.fx[ip].data.name === name) { 326 | return this.fx[ip]; 327 | } 328 | } 329 | }; 330 | 331 | function OMGSongPart(data, headPart) { 332 | 333 | this.fx = []; 334 | 335 | this.data = data || {}; 336 | this.data.type = this.data.type || "PART"; 337 | 338 | if (!this.data.fx) { 339 | this.data.fx = []; 340 | } 341 | 342 | // if no headPart is given, this is a headPart, and we need just the header data 343 | if (!headPart) { 344 | if (!this.data.surface) { 345 | if (this.data.soundSet && this.data.soundSet.defaultSurface) { 346 | this.data.surface = {url: this.data.soundSet.defaultSurface}; 347 | } 348 | else { 349 | this.data.surface = {url: "PRESET_VERTICAL"}; 350 | } 351 | } 352 | 353 | if (!this.data.soundSet) { 354 | 355 | // there might be a soundSet for this part in the song's data 356 | /*if (this.section.song.data.soundSets && this.section.song.data.soundSets[this.data.name]) { 357 | this.data.soundSet = this.section.song.data.soundSets[this.data.name] 358 | } 359 | else {*/ 360 | this.data.soundSet = { 361 | name: "Sine Oscillator", 362 | url: "PRESET_OSC_SINE", 363 | highNote: 108, 364 | lowNote: 0, 365 | chromatic: true, 366 | octave: 5 367 | }; 368 | //} 369 | } 370 | 371 | this.setSoundSet(this.data.soundSet) 372 | 373 | if (!this.data.name) { 374 | this.data.name = this.data.soundSet.name; 375 | } 376 | 377 | this.makeAudioParams(false, (this.data.soundSet.url || "").startsWith("PRESET_OSC")); 378 | 379 | } 380 | else { 381 | // otherwise we need the detail data 382 | 383 | this.data.name = headPart.data.name 384 | this.headPart = headPart 385 | this.surface = headPart.data.surface 386 | 387 | if (this.surface.url === "PRESET_VERTICAL") { 388 | if (!this.data.notes) { 389 | this.data.notes = []; 390 | } 391 | } 392 | else if (this.surface.url === "PRESET_SEQUENCER") { 393 | if (!this.data.tracks) { 394 | this.makeTracks(); 395 | } 396 | for (var i = 0; i < this.data.tracks.length; i++) { 397 | this.makeAudioParams(this.data.tracks[i]); 398 | } 399 | } 400 | 401 | } 402 | 403 | this.notesPlaying = {} 404 | 405 | if (this.data.id) { 406 | this.saved = true; 407 | } 408 | 409 | } 410 | 411 | OMGSongPart.prototype.makeAudioParams = function (track, osc) { 412 | var obj = track || this.data; 413 | if (!obj.audioParams) obj.audioParams = {}; 414 | 415 | //backwards compat, now we use gain instead of volume 416 | if (typeof obj.audioParams.volume === "number" && 417 | typeof obj.audioParams.gain !== "number") { 418 | obj.audioParams.gain = Math.pow(obj.audioParams.volume, 2); 419 | } 420 | if (typeof obj.audioParams.gain !== "number") { 421 | obj.audioParams.gain = track ? 1 : osc ? 0.2 : 0.6; 422 | } 423 | if (typeof obj.audioParams.pan !== "number") 424 | obj.audioParams.pan = 0; 425 | if (typeof obj.audioParams.warp !== "number") 426 | obj.audioParams.warp = 1; 427 | }; 428 | 429 | 430 | OMGSongPart.prototype.makeTracks = function () { 431 | this.data.tracks = []; 432 | //todo maybe do the prefix postfix stuff once and store it in the header? 433 | var soundSet = this.headPart.data.soundSet 434 | if (soundSet && soundSet.data) { 435 | var that = this; 436 | soundSet.data.forEach(function (sound) { 437 | var track = {name: sound.name, data: [], 438 | audioParams: {gain: 1, pan: 0, warp: 1}}; 439 | track.sound = (soundSet.prefix || "") + 440 | sound.url + (soundSet.postfix || ""); 441 | that.data.tracks.push(track); 442 | }); 443 | } 444 | }; 445 | 446 | OMGSongPart.prototype.getFX = function (name) { 447 | for (var ip = 0; ip < this.fx.length; ip++) { 448 | if (this.fx[ip].data.name === name) { 449 | return this.fx[ip]; 450 | } 451 | } 452 | }; 453 | 454 | OMGSongPart.prototype.rescale = function (keyParams, chord) { 455 | 456 | var data = this.data; 457 | if (!data || !data.notes) { 458 | return; 459 | } 460 | 461 | if (typeof data.soundSet.octave !== "number") { 462 | 463 | } 464 | 465 | var octave = data.soundSet.octave; 466 | var octaves2; 467 | var newNote; 468 | var onote; 469 | 470 | for (var i = 0; i < data.notes.length; i++) { 471 | onote = data.notes[i]; 472 | if (onote.rest || 473 | typeof onote.note !== "number" || 474 | onote.note !== Math.round(onote.note)) { 475 | continue; 476 | } 477 | 478 | newNote = onote.note + chord; 479 | octaves2 = 0; 480 | while (newNote >= keyParams.scale.length) { 481 | newNote = newNote - keyParams.scale.length; 482 | octaves2++; 483 | } 484 | while (newNote < 0) { 485 | newNote = newNote + keyParams.scale.length; 486 | octaves2--; 487 | } 488 | 489 | newNote = keyParams.scale[newNote] + octaves2 * 12 + 490 | octave * 12 + keyParams.rootNote; 491 | 492 | onote.scaledNote = newNote; 493 | 494 | } 495 | }; 496 | 497 | OMGSongPart.prototype.setSoundSet = function (ss) { 498 | 499 | if (!ss || !ss.data) 500 | return; 501 | 502 | this.data.soundSet = ss 503 | 504 | var topNote = ss.highNote; 505 | if (!topNote && ss.data.length) { 506 | topNote = ss.lowNote + ss.data.length - 1; 507 | } 508 | if (!ss.octave) { 509 | ss.octave = Math.floor((topNote + ss.lowNote) / 2 / 12); 510 | } 511 | 512 | this.soundUrls = [] 513 | var noteIndex 514 | for (var i = 0; i < ss.data.length; i++) { 515 | if (ss.chromatic) { 516 | noteIndex = i + ss.lowNote 517 | } 518 | else { 519 | noteIndex = i 520 | } 521 | 522 | if (ss.data[i]) { 523 | this.soundUrls[noteIndex] = (ss.prefix || "") + ss.data[i].url + (ss.postfix || ""); 524 | } 525 | } 526 | 527 | // todo do we really need this? It's used... but shouldn't be 528 | this.soundSet = ss; 529 | }; 530 | 531 | OMGSongPart.prototype.getData = function (options) { 532 | var json = JSON.parse(JSON.stringify(this.data)) 533 | if (options && options.standalone) { 534 | json.beatParams = JSON.parse(JSON.stringify(this.section.song.data.beatParams)) 535 | json.keyParams = JSON.parse(JSON.stringify(this.section.song.data.keyParams)) 536 | } 537 | return json 538 | } 539 | 540 | OMGSongPart.prototype.change = function (track, subbeat, value) { 541 | //TODO are we gonna change it? 542 | this.section.song.partChanged(this, track, subbeat, value) 543 | } -------------------------------------------------------------------------------- /www/js/libs/NoSleep.min.js: -------------------------------------------------------------------------------- 1 | /*! NoSleep.min.js v0.9.0 - git.io/vfn01 - Rich Tibbett - MIT license */ 2 | !function(A,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.NoSleep=e():A.NoSleep=e()}("undefined"!=typeof self?self:this,function(){return function(A){function e(B){if(o[B])return o[B].exports;var Q=o[B]={i:B,l:!1,exports:{}};return A[B].call(Q.exports,Q,Q.exports,e),Q.l=!0,Q.exports}var o={};return e.m=A,e.c=o,e.d=function(A,o,B){e.o(A,o)||Object.defineProperty(A,o,{configurable:!1,enumerable:!0,get:B})},e.n=function(A){var o=A&&A.__esModule?function(){return A.default}:function(){return A};return e.d(o,"a",o),o},e.o=function(A,e){return Object.prototype.hasOwnProperty.call(A,e)},e.p="",e(e.s=0)}([function(A,e,o){"use strict";function B(A,e){if(!(A instanceof e))throw new TypeError("Cannot call a class as a function")}var Q=function(){function A(A,e){for(var o=0;o.5&&(e.noSleepVideo.currentTime=Math.random())})}))}return Q(A,[{key:"_addSourceToVideo",value:function(A,e,o){var B=document.createElement("source");B.src=o,B.type="video/"+e,A.appendChild(B)}},{key:"enable",value:function(){E?(this.disable(),console.warn("\n NoSleep enabled for older iOS devices. This can interrupt\n active or long-running network requests from completing successfully.\n See https://github.com/richtr/NoSleep.js/issues/15 for more details.\n "),this.noSleepTimer=window.setInterval(function(){document.hidden||(window.location.href=window.location.href.split("#")[0],window.setTimeout(window.stop,0))},15e3)):this.noSleepVideo.play()}},{key:"disable",value:function(){E?this.noSleepTimer&&(console.warn("\n NoSleep now disabled for older iOS devices.\n "),window.clearInterval(this.noSleepTimer),this.noSleepTimer=null):this.noSleepVideo.pause()}}]),A}();A.exports=l},function(A,e,o){"use strict";A.exports={webm:"data:video/webm;base64,GkXfo0AgQoaBAUL3gQFC8oEEQvOBCEKCQAR3ZWJtQoeBAkKFgQIYU4BnQI0VSalmQCgq17FAAw9CQE2AQAZ3aGFtbXlXQUAGd2hhbW15RIlACECPQAAAAAAAFlSua0AxrkAu14EBY8WBAZyBACK1nEADdW5khkAFVl9WUDglhohAA1ZQOIOBAeBABrCBCLqBCB9DtnVAIueBAKNAHIEAAIAwAQCdASoIAAgAAUAmJaQAA3AA/vz0AAA=",mp4:"data:video/mp4;base64,AAAAIGZ0eXBtcDQyAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAACKBtZGF0AAAC8wYF///v3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE0MiByMjQ3OSBkZDc5YTYxIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTEgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MToweDExMSBtZT1oZXggc3VibWU9MiBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0wIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MCA4eDhkY3Q9MCBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0wIHRocmVhZHM9NiBsb29rYWhlYWRfdGhyZWFkcz0xIHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9MCBiZnJhbWVzPTMgYl9weXJhbWlkPTIgYl9hZGFwdD0xIGJfYmlhcz0wIGRpcmVjdD0xIHdlaWdodGI9MSBvcGVuX2dvcD0wIHdlaWdodHA9MSBrZXlpbnQ9MzAwIGtleWludF9taW49MzAgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD0xMCByYz1jcmYgbWJ0cmVlPTEgY3JmPTIwLjAgcWNvbXA9MC42MCBxcG1pbj0wIHFwbWF4PTY5IHFwc3RlcD00IHZidl9tYXhyYXRlPTIwMDAwIHZidl9idWZzaXplPTI1MDAwIGNyZl9tYXg9MC4wIG5hbF9ocmQ9bm9uZSBmaWxsZXI9MCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAOWWIhAA3//p+C7v8tDDSTjf97w55i3SbRPO4ZY+hkjD5hbkAkL3zpJ6h/LR1CAABzgB1kqqzUorlhQAAAAxBmiQYhn/+qZYADLgAAAAJQZ5CQhX/AAj5IQADQGgcIQADQGgcAAAACQGeYUQn/wALKCEAA0BoHAAAAAkBnmNEJ/8ACykhAANAaBwhAANAaBwAAAANQZpoNExDP/6plgAMuSEAA0BoHAAAAAtBnoZFESwr/wAI+SEAA0BoHCEAA0BoHAAAAAkBnqVEJ/8ACykhAANAaBwAAAAJAZ6nRCf/AAsoIQADQGgcIQADQGgcAAAADUGarDRMQz/+qZYADLghAANAaBwAAAALQZ7KRRUsK/8ACPkhAANAaBwAAAAJAZ7pRCf/AAsoIQADQGgcIQADQGgcAAAACQGe60Qn/wALKCEAA0BoHAAAAA1BmvA0TEM//qmWAAy5IQADQGgcIQADQGgcAAAAC0GfDkUVLCv/AAj5IQADQGgcAAAACQGfLUQn/wALKSEAA0BoHCEAA0BoHAAAAAkBny9EJ/8ACyghAANAaBwAAAANQZs0NExDP/6plgAMuCEAA0BoHAAAAAtBn1JFFSwr/wAI+SEAA0BoHCEAA0BoHAAAAAkBn3FEJ/8ACyghAANAaBwAAAAJAZ9zRCf/AAsoIQADQGgcIQADQGgcAAAADUGbeDRMQz/+qZYADLkhAANAaBwAAAALQZ+WRRUsK/8ACPghAANAaBwhAANAaBwAAAAJAZ+1RCf/AAspIQADQGgcAAAACQGft0Qn/wALKSEAA0BoHCEAA0BoHAAAAA1Bm7w0TEM//qmWAAy4IQADQGgcAAAAC0Gf2kUVLCv/AAj5IQADQGgcAAAACQGf+UQn/wALKCEAA0BoHCEAA0BoHAAAAAkBn/tEJ/8ACykhAANAaBwAAAANQZvgNExDP/6plgAMuSEAA0BoHCEAA0BoHAAAAAtBnh5FFSwr/wAI+CEAA0BoHAAAAAkBnj1EJ/8ACyghAANAaBwhAANAaBwAAAAJAZ4/RCf/AAspIQADQGgcAAAADUGaJDRMQz/+qZYADLghAANAaBwAAAALQZ5CRRUsK/8ACPkhAANAaBwhAANAaBwAAAAJAZ5hRCf/AAsoIQADQGgcAAAACQGeY0Qn/wALKSEAA0BoHCEAA0BoHAAAAA1Bmmg0TEM//qmWAAy5IQADQGgcAAAAC0GehkUVLCv/AAj5IQADQGgcIQADQGgcAAAACQGepUQn/wALKSEAA0BoHAAAAAkBnqdEJ/8ACyghAANAaBwAAAANQZqsNExDP/6plgAMuCEAA0BoHCEAA0BoHAAAAAtBnspFFSwr/wAI+SEAA0BoHAAAAAkBnulEJ/8ACyghAANAaBwhAANAaBwAAAAJAZ7rRCf/AAsoIQADQGgcAAAADUGa8DRMQz/+qZYADLkhAANAaBwhAANAaBwAAAALQZ8ORRUsK/8ACPkhAANAaBwAAAAJAZ8tRCf/AAspIQADQGgcIQADQGgcAAAACQGfL0Qn/wALKCEAA0BoHAAAAA1BmzQ0TEM//qmWAAy4IQADQGgcAAAAC0GfUkUVLCv/AAj5IQADQGgcIQADQGgcAAAACQGfcUQn/wALKCEAA0BoHAAAAAkBn3NEJ/8ACyghAANAaBwhAANAaBwAAAANQZt4NExC//6plgAMuSEAA0BoHAAAAAtBn5ZFFSwr/wAI+CEAA0BoHCEAA0BoHAAAAAkBn7VEJ/8ACykhAANAaBwAAAAJAZ+3RCf/AAspIQADQGgcAAAADUGbuzRMQn/+nhAAYsAhAANAaBwhAANAaBwAAAAJQZ/aQhP/AAspIQADQGgcAAAACQGf+UQn/wALKCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHAAACiFtb292AAAAbG12aGQAAAAA1YCCX9WAgl8AAAPoAAAH/AABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAGGlvZHMAAAAAEICAgAcAT////v7/AAAF+XRyYWsAAABcdGtoZAAAAAPVgIJf1YCCXwAAAAEAAAAAAAAH0AAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAygAAAMoAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAB9AAABdwAAEAAAAABXFtZGlhAAAAIG1kaGQAAAAA1YCCX9WAgl8AAV+QAAK/IFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAUcbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAAE3HN0YmwAAACYc3RzZAAAAAAAAAABAAAAiGF2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAygDKAEgAAABIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY//8AAAAyYXZjQwFNQCj/4QAbZ01AKOyho3ySTUBAQFAAAAMAEAAr8gDxgxlgAQAEaO+G8gAAABhzdHRzAAAAAAAAAAEAAAA8AAALuAAAABRzdHNzAAAAAAAAAAEAAAABAAAB8GN0dHMAAAAAAAAAPAAAAAEAABdwAAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAAC7gAAAAAQAAF3AAAAABAAAAAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAEEc3RzegAAAAAAAAAAAAAAPAAAAzQAAAAQAAAADQAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAANAAAADQAAAQBzdGNvAAAAAAAAADwAAAAwAAADZAAAA3QAAAONAAADoAAAA7kAAAPQAAAD6wAAA/4AAAQXAAAELgAABEMAAARcAAAEbwAABIwAAAShAAAEugAABM0AAATkAAAE/wAABRIAAAUrAAAFQgAABV0AAAVwAAAFiQAABaAAAAW1AAAFzgAABeEAAAX+AAAGEwAABiwAAAY/AAAGVgAABnEAAAaEAAAGnQAABrQAAAbPAAAG4gAABvUAAAcSAAAHJwAAB0AAAAdTAAAHcAAAB4UAAAeeAAAHsQAAB8gAAAfjAAAH9gAACA8AAAgmAAAIQQAACFQAAAhnAAAIhAAACJcAAAMsdHJhawAAAFx0a2hkAAAAA9WAgl/VgIJfAAAAAgAAAAAAAAf8AAAAAAAAAAAAAAABAQAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAACsm1kaWEAAAAgbWRoZAAAAADVgIJf1YCCXwAArEQAAWAAVcQAAAAAACdoZGxyAAAAAAAAAABzb3VuAAAAAAAAAAAAAAAAU3RlcmVvAAAAAmNtaW5mAAAAEHNtaGQAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAidzdGJsAAAAZ3N0c2QAAAAAAAAAAQAAAFdtcDRhAAAAAAAAAAEAAAAAAAAAAAACABAAAAAArEQAAAAAADNlc2RzAAAAAAOAgIAiAAIABICAgBRAFQAAAAADDUAAAAAABYCAgAISEAaAgIABAgAAABhzdHRzAAAAAAAAAAEAAABYAAAEAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAAUc3RzegAAAAAAAAAGAAAAWAAAAXBzdGNvAAAAAAAAAFgAAAOBAAADhwAAA5oAAAOtAAADswAAA8oAAAPfAAAD5QAAA/gAAAQLAAAEEQAABCgAAAQ9AAAEUAAABFYAAARpAAAEgAAABIYAAASbAAAErgAABLQAAATHAAAE3gAABPMAAAT5AAAFDAAABR8AAAUlAAAFPAAABVEAAAVXAAAFagAABX0AAAWDAAAFmgAABa8AAAXCAAAFyAAABdsAAAXyAAAF+AAABg0AAAYgAAAGJgAABjkAAAZQAAAGZQAABmsAAAZ+AAAGkQAABpcAAAauAAAGwwAABskAAAbcAAAG7wAABwYAAAcMAAAHIQAABzQAAAc6AAAHTQAAB2QAAAdqAAAHfwAAB5IAAAeYAAAHqwAAB8IAAAfXAAAH3QAAB/AAAAgDAAAICQAACCAAAAg1AAAIOwAACE4AAAhhAAAIeAAACH4AAAiRAAAIpAAACKoAAAiwAAAItgAACLwAAAjCAAAAFnVkdGEAAAAObmFtZVN0ZXJlbwAAAHB1ZHRhAAAAaG1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAAO2lsc3QAAAAzqXRvbwAAACtkYXRhAAAAAQAAAABIYW5kQnJha2UgMC4xMC4yIDIwMTUwNjExMDA="}}])}); -------------------------------------------------------------------------------- /www/js/libs/peakmeter.js: -------------------------------------------------------------------------------- 1 | //hacked from 2 | //https://github.com/esonderegger/web-audio-peak-meter/blob/master/index.js 3 | //https://stackoverflow.com/questions/44360301/web-audio-api-creating-a-peak-meter-with-analysernode 4 | 5 | var PeakMeter = function (audioNode, div, context) { 6 | this.analyser = context.createAnalyser(); 7 | this.audioNode = audioNode; 8 | this.div = div; 9 | audioNode.connect(this.analyser); 10 | 11 | this.sampleBuffer = new Float32Array(this.analyser.fftSize); 12 | 13 | this.width = div.clientWidth; 14 | this.height = div.clientHeight; 15 | this.meterElement = this.createContainerDiv(div); 16 | 17 | this.vertical = true; //this.width < this.height; 18 | this.createRainbow(this.meterElement, this.width, this.height, 0, 0); 19 | this.channelCount = 1; //this.analyser.channelCount; 20 | this.channelWidth = this.width / this.channelCount; 21 | if (!this.vertical) { 22 | this.channelWidth = this.height / this.channelCount; 23 | } 24 | 25 | this.channelMasks = []; 26 | this.channelPeaks = []; 27 | this.maskSizes = []; 28 | this.channelLeft = 0; 29 | 30 | for (var i = 0; i < this.channelCount; i++) { 31 | //this.createChannelMask(meterElement, this.channelWidth, 0, 0, 0, false); 32 | this.channelMasks[i] = this.createChannelMask(this.meterElement, this.channelWidth, 33 | 0, this.channelLeft, 0); 34 | this.channelPeaks[i] = 0.0; 35 | 36 | this.channelLeft += this.channelWidth; 37 | this.maskSizes[i] = 0; 38 | } 39 | 40 | }; 41 | 42 | PeakMeter.prototype.remove = function () { 43 | this.div.removeChild(this.meterElement); 44 | this.audioNode.disconnect(this.analyser); 45 | }; 46 | 47 | 48 | PeakMeter.prototype.options = { 49 | borderSize: 0, 50 | fontSize: 9, 51 | backgroundColor: 'black', 52 | tickColor: '#ddd', 53 | gradient: ['red 1%', '#ff0 16%', 'lime 45%', '#080 100%'], 54 | dbRange: 48, 55 | dbTickSize: 6, 56 | maskTransition: '0.1s', 57 | }; 58 | 59 | 60 | 61 | PeakMeter.prototype.createContainerDiv = function(parent) { 62 | var meterElement = document.createElement('div'); 63 | meterElement.style.position = 'absolute'; 64 | meterElement.style.width = '100%'; 65 | meterElement.style.height = '100%'; 66 | meterElement.style.top = "0px"; 67 | meterElement.style.left = "0px"; 68 | 69 | //meterElement.style.backgroundColor = "yellow"; //options.backgroundColor; 70 | parent.appendChild(meterElement); 71 | return meterElement; 72 | }; 73 | 74 | PeakMeter.prototype.createRainbow = function(parent, width, height, top, left) { 75 | var rainbow = document.createElement('div'); 76 | parent.appendChild(rainbow); 77 | rainbow.style.width = '100%'; 78 | rainbow.style.height = '100%'; 79 | rainbow.style.position = 'absolute'; 80 | rainbow.style.top = top + 'px'; 81 | if (this.vertical) { 82 | rainbow.style.left = left + 'px'; 83 | var gradientStyle = 'linear-gradient(to bottom, ' + 84 | this.options.gradient.join(', ') + ')'; 85 | } else { 86 | rainbow.style.left = this.options.borderSize + 'px'; 87 | var gradientStyle = 'linear-gradient(to left, ' + 88 | this.options.gradient.join(', ') + ')'; 89 | } 90 | rainbow.style.backgroundImage = gradientStyle; 91 | return rainbow; 92 | }; 93 | 94 | PeakMeter.prototype.createChannelMask = function(parent, width, top, left, transition) { 95 | var channelMask = document.createElement('div'); 96 | parent.appendChild(channelMask); 97 | channelMask.style.position = 'absolute'; 98 | if (this.vertical) { 99 | channelMask.style.width = '100%'; 100 | channelMask.style.height = this.height + 'px'; 101 | channelMask.style.top = top + 'px'; 102 | channelMask.style.left = left + 'px'; 103 | } else { 104 | channelMask.style.width = this.width + 'px'; 105 | channelMask.style.height = width + 'px'; 106 | channelMask.style.top = left + 'px'; 107 | channelMask.style.right = this.options.fontSize * 2 + 'px'; 108 | } 109 | channelMask.style.backgroundColor = this.options.backgroundColor; 110 | if (transition) { 111 | if (this.vertical) { 112 | channelMask.style.transition = 'height ' + this.options.maskTransition; 113 | } else { 114 | channelMask.style.transition = 'width ' + this.options.maskTransition; 115 | } 116 | } 117 | return channelMask; 118 | }; 119 | 120 | PeakMeter.prototype.maskSize = function(floatVal) { 121 | var meterDimension = this.vertical ? this.height : this.width; 122 | if (floatVal === -Infinity) { 123 | return meterDimension; 124 | } else { 125 | var d = this.options.dbRange * -1; 126 | var returnVal = floatVal * meterDimension / d; 127 | if (returnVal > meterDimension) { 128 | return meterDimension; 129 | } else { 130 | return returnVal; 131 | } 132 | } 133 | }; 134 | 135 | 136 | PeakMeter.prototype.updateMeter = function () { 137 | // console.log(this.analyser) 138 | this.analyser.getFloatTimeDomainData(this.sampleBuffer); 139 | 140 | // Compute average power over the interval. 141 | /*let sumOfSquares = 0; 142 | for (let i = 0; i < this.sampleBuffer.length; i++) { 143 | sumOfSquares += this.sampleBuffer[i] ** 2; 144 | } 145 | const avgPowerDecibels = 10 * Math.log10(sumOfSquares / this.sampleBuffer.length);*/ 146 | 147 | // Compute peak instantaneous power over the interval. 148 | this.peakInstantaneousPower = 0; 149 | for (this.update_i = 0; this.update_i < this.sampleBuffer.length; this.update_i++) { 150 | this.peakInstantaneousPower = Math.max(this.sampleBuffer[this.update_i] ** 2, this.peakInstantaneousPower); 151 | } 152 | this.peakInstantaneousPowerDecibels = 10 * Math.log10(this.peakInstantaneousPower); 153 | 154 | this.channelMasks[0].style.height = this.maskSize(this.peakInstantaneousPowerDecibels, this.height) + 'px'; 155 | 156 | }; 157 | 158 | 159 | 160 | /* 161 | * 162 | for (var i = 0; i < meter.channelCount; i++) { 163 | if (meter.vertical) { 164 | //meter.channelMasks[i].style.height = meter.maskSizes[i] + 'px'; 165 | } else { 166 | //meter.channelMasks[i].style.width = meter.maskSizes[i] + 'px'; 167 | } 168 | //channelPeakLabels[i].textContent = textLabels[i]; 169 | } 170 | 171 | */ -------------------------------------------------------------------------------- /www/js/libs/peakmeter_basic.js: -------------------------------------------------------------------------------- 1 | //hacked from 2 | //https://github.com/esonderegger/web-audio-peak-meter/blob/master/index.js 3 | //https://stackoverflow.com/questions/44360301/web-audio-api-creating-a-peak-meter-with-analysernode 4 | 5 | function BasicPeakMeter (audioNode, div, context) { 6 | this.analyser = context.createAnalyser(); 7 | this.audioNode = audioNode; 8 | this.div = div; 9 | audioNode.connect(this.analyser); 10 | 11 | this.sampleBuffer = new Float32Array(this.analyser.fftSize); 12 | 13 | this.width = div.clientWidth; 14 | this.height = div.clientHeight; 15 | 16 | this.vertical = this.width < this.height; 17 | this.channelCount = 1; //this.analyser.channelCount; 18 | 19 | this.meterBar = document.createElement("div") 20 | this.meterBar.style.position = "absolute" 21 | this.meterBar.style.bottom = "0px" 22 | this.meterBar.style.width = "100%" 23 | this.meterBar.style.height = "0px" 24 | this.meterBar.style.backgroundColor = "green" 25 | 26 | this.div.appendChild(this.meterBar) 27 | }; 28 | 29 | BasicPeakMeter.prototype.remove = function () { 30 | this.div.removeChild(this.meterElement); 31 | this.audioNode.disconnect(this.analyser); 32 | }; 33 | 34 | 35 | BasicPeakMeter.prototype.updateMeter = function () { 36 | this.height = this.div.clientHeight 37 | 38 | // Compute peak instantaneous power over the interval. 39 | this.analyser.getFloatTimeDomainData(this.sampleBuffer); 40 | this.peakInstantaneousPower = 0; 41 | for (this.update_i = 0; this.update_i < this.sampleBuffer.length; this.update_i++) { 42 | this.peakInstantaneousPower = Math.max(this.sampleBuffer[this.update_i] ** 2, this.peakInstantaneousPower); 43 | } 44 | this.peakInstantaneousPowerDecibels = 10 * Math.log10(this.peakInstantaneousPower); 45 | 46 | this.meterBar.style.height = this.div.clientHeight - 47 | (this.peakInstantaneousPowerDecibels === -Infinity ? 48 | this.height : 49 | Math.min(this.height, this.peakInstantaneousPowerDecibels * this.height / -48)) 50 | + "px" 51 | 52 | }; 53 | -------------------------------------------------------------------------------- /www/js/monkey.js: -------------------------------------------------------------------------------- 1 | function OMGMonkey(song, section) { 2 | 3 | this.song = song; 4 | this.section = section; 5 | this.loop = 0; 6 | 7 | var monkey = this; 8 | this.changeables = [ 9 | {name: "Notes and Beats", probability: 0, functions: monkey.getPartDataFunctions()}, 10 | {name: "Tempo", probability: 0, functions: monkey.getTempoFunctions()}, 11 | {name: "Mute", probability: 0, functions: monkey.getMuteFunctions()}, 12 | {name: "Volume/Pan", probability: 0, functions: monkey.getVolumePanFunctions()}, 13 | {name: "Chords", probability: 0, functions: monkey.getChordsFunctions()}, 14 | {name: "New Part", probability: 0, functions: monkey.getNewPartFunctions()}, 15 | {name: "Key Signature", probability: 0, functions: monkey.getKeyFunctions()} 16 | ]; 17 | } 18 | 19 | OMGMonkey.prototype.randomize = function () { 20 | var monkey = this; 21 | this.changeables.forEach((changeable) => { 22 | if (Math.random() < changeable.probability) { 23 | monkey.change(changeable); 24 | } 25 | }); 26 | 27 | if (this.loop > 0) { 28 | setTimeout(function () { 29 | monkey.randomize(); 30 | }, this.loop * 1000); 31 | } 32 | 33 | }; 34 | 35 | OMGMonkey.prototype.getRandomI = function (array) { 36 | return i = Math.floor(Math.random() * array.length); 37 | }; 38 | OMGMonkey.prototype.getRandomElement = function (array) { 39 | return array[this.getRandomI(array)]; 40 | }; 41 | 42 | OMGMonkey.prototype.change = function (changeable) { 43 | this.getRandomElement(changeable.functions)(this.song); 44 | }; 45 | 46 | OMGMonkey.prototype.getMuteFunctions = function () { 47 | var monkey = this; 48 | return [ 49 | function () { 50 | monkey.forRandomRandomParts(function (part) { 51 | part.data.audioParams.mute = 52 | !part.data.audioParams.mute; 53 | monkey.song.partMuteChanged(part); 54 | }); 55 | } 56 | ]}; 57 | 58 | OMGMonkey.prototype.forOneRandomPart = function (callback) { 59 | if (!this.section.parts.length) return; 60 | var partI = Math.floor(Math.random() * this.section.parts.length); 61 | callback(this.section.parts[partI]); 62 | }; 63 | OMGMonkey.prototype.forRandomParts = function (callback) { 64 | if (!this.section.parts.length) return; 65 | var monkey = this; 66 | monkey.section.parts.forEach(part => { 67 | if (Math.random() < 1 / monkey.section.parts.length) { 68 | callback(part); 69 | } 70 | }); 71 | }; 72 | OMGMonkey.prototype.forRandomRandomParts = function (callback) { 73 | if (Math.random() > 0.5) { 74 | this.forOneRandomPart(callback); 75 | } 76 | else { 77 | this.forRandomParts(callback); 78 | } 79 | }; 80 | 81 | 82 | OMGMonkey.prototype.getKeyFunctions = function () { 83 | var song = this.song; 84 | return [ 85 | function () { 86 | song.data.keyParams.rootNote = Math.floor(Math.random() * 12); 87 | song.keyChanged(); 88 | }, 89 | function () { 90 | song.data.keyParams.rootNote += 1 + Math.floor(Math.random() * 2); 91 | song.data.keyParams.rootNote = song.data.keyParams.rootNote % 12; 92 | song.keyChanged(); 93 | }, 94 | function () { 95 | song.data.keyParams.rootNote -= 1 + Math.floor(Math.random() * 2); 96 | if (song.data.keyParams.rootNote < 0) song.data.keyParams.rootNote = 11; 97 | song.keyChanged(); 98 | }, 99 | function () { 100 | song.data.keyParams.scale = omg.ui.scales[Math.floor(Math.random() * omg.ui.scales.length)].value; 101 | song.keyChanged(); 102 | }, 103 | function () { 104 | song.data.keyParams.rootNote = Math.floor(Math.random() * 12); 105 | song.data.keyParams.scale = omg.ui.scales[Math.floor(Math.random() * omg.ui.scales.length)].value; 106 | song.keyChanged(); 107 | } 108 | ]}; 109 | 110 | OMGMonkey.prototype.getTempoFunctions = function () { 111 | var monkey = this; 112 | return [ 113 | function (song) { 114 | if (monkey.song.data.beatParams.shuffle == 0) { 115 | monkey.changeShuffle(30); 116 | } 117 | else if (monkey.song.data.beatParams.shuffle > 0.15) { 118 | monkey.changeShuffle(monkey.song.data.beatParams.shuffle * -100); 119 | } 120 | else { 121 | monkey.changeShuffle(2); 122 | } 123 | }, 124 | function (song) { 125 | monkey.changeShuffle(Math.round(Math.random() * 2) - 1); 126 | }, 127 | function (song) { 128 | monkey.changeShuffle(Math.round(Math.random() * 4) - 2); 129 | }, 130 | function (song) { 131 | monkey.changeShuffle(Math.round(Math.random() * 10) - 5); 132 | }, 133 | function (song) { 134 | monkey.changeTempo(Math.round(Math.random() * 2) - 1); 135 | }, 136 | function (song) { 137 | monkey.changeTempo(Math.round(Math.random() * 4) - 2); 138 | }, 139 | function (song) { 140 | monkey.changeTempo(Math.round(Math.random() * 10) - 5); 141 | }, 142 | function (song) { 143 | monkey.changeTempo(Math.round(Math.random() * 20) - 10); 144 | }, 145 | function (song) { 146 | monkey.changeTempo(Math.round(Math.random() * 40) - 20); 147 | } 148 | ]}; 149 | 150 | OMGMonkey.prototype.changeTempo = function (bpmChange) { 151 | this.song.data.beatParams.bpm = Math.min(Math.max(this.song.data.beatParams.bpm + bpmChange, 40), 200); 152 | this.song.beatsChanged(); 153 | }; 154 | OMGMonkey.prototype.changeShuffle = function (change) { 155 | this.song.data.beatParams.shuffle = Math.min(Math.max(this.song.data.beatParams.shuffle + change / 100, 0), 0.50); 156 | this.song.beatsChanged(); 157 | }; 158 | 159 | OMGMonkey.prototype.getVolumePanFunctions = function () { 160 | var monkey = this; 161 | return [ 162 | function () { 163 | monkey.forRandomRandomParts(function (part) { 164 | monkey.changePan(part, Math.random() * Math.random() > 0.5 ? 1 : -1); 165 | }); 166 | }, 167 | function () { 168 | monkey.forRandomRandomParts(function (part) { 169 | monkey.changeVolume(part, Math.random() * Math.random() > 0.5 ? 1 : -1); 170 | }); 171 | }, 172 | function () { 173 | monkey.forRandomRandomParts(function (part) { 174 | monkey.changePan(part, Math.round(Math.random() * 2) - 1); 175 | }); 176 | }, 177 | function () { 178 | monkey.forRandomRandomParts(function (part) { 179 | monkey.changePan(part, Math.round(Math.random() * 4) - 2); 180 | }); 181 | }, 182 | function () { 183 | monkey.forRandomRandomParts(function (part) { 184 | monkey.changePan(part, Math.round(Math.random() * 10) - 5); 185 | }); 186 | }, 187 | function () { 188 | monkey.forRandomRandomParts(function (part) { 189 | monkey.changeVolume(part, Math.round(Math.random() * 2) - 1); 190 | }); 191 | }, 192 | function () { 193 | monkey.forRandomRandomParts(function (part) { 194 | monkey.changeVolume(part, Math.round(Math.random() * 4) - 2); 195 | }); 196 | }, 197 | function () { 198 | monkey.forRandomRandomParts(function (part) { 199 | monkey.changeVolume(part, Math.round(Math.random() * 10) - 5); 200 | }); 201 | }, 202 | function () { 203 | monkey.forRandomRandomParts(function (part) { 204 | monkey.changeVolume(part, Math.round(Math.random() * 20) - 10); 205 | }); 206 | }, 207 | function () { 208 | monkey.forRandomRandomParts(function (part) { 209 | monkey.changeVolume(part, Math.round(Math.random() * 40) - 20); 210 | }); 211 | } 212 | ]; 213 | }; 214 | 215 | OMGMonkey.prototype.changeVolume = function (part, change) { 216 | part.data.audioParams.gain = Math.min(Math.max(part.data.audioParams.gain + change / 10, 0.1), 0.95); 217 | this.song.partMuteChanged(part); 218 | }; 219 | OMGMonkey.prototype.changePan = function (part, change) { 220 | part.data.audioParams.pan = Math.min(Math.max(part.data.audioParams.pan + change / 10, -1), 1); 221 | this.song.partMuteChanged(part); 222 | }; 223 | 224 | 225 | 226 | OMGMonkey.prototype.getChordsFunctions = function () { 227 | var monkey = this; 228 | var scale = monkey.song.data.keyParams.scale 229 | return [ 230 | function () { 231 | var n = monkey.randomChord(scale); 232 | monkey.changeChords([n]); 233 | }, 234 | function () { 235 | monkey.changeChords([0]); 236 | }, 237 | function () { 238 | monkey.changeChords([-1]); 239 | }, 240 | function () { 241 | var a = []; 242 | var n = Math.floor(Math.random() * 5); 243 | for (var i = 0; i < n; i++) { 244 | a.push(monkey.randomChord(scale)); 245 | } 246 | monkey.changeChords(a); 247 | } 248 | ]; 249 | }; 250 | 251 | OMGMonkey.prototype.randomChord = function (scale) { 252 | var n = scale.length; 253 | n = n * 2 - 1; 254 | n = Math.floor(Math.random() * n); 255 | n = n - scale.length + 1; 256 | return n; 257 | }; 258 | 259 | OMGMonkey.prototype.changeChords = function (chords) { 260 | this.section.data.chordProgression = chords; 261 | this.song.chordProgressionChanged(this.section); 262 | }; 263 | 264 | 265 | OMGMonkey.prototype.getNewPartFunctions = function () { 266 | var monkey = this; 267 | //pick a random part from the gallery 268 | //pick a random soundset from the gallery and fill it 269 | return [ 270 | function () { 271 | //todo get soundfonts from somewhere else 272 | var sfs = ["https://gleitz.github.io/midi-js-soundfonts/MusyngKite/", 273 | "https://gleitz.github.io/midi-js-soundfonts/FluidR3_GM/"]; 274 | var sf = sfs[Math.floor(Math.random() * sfs.length)]; 275 | var i = Math.floor(Math.random() * omg.ui.soundFontNames.length); 276 | var s = omg.ui.soundFontNames[i]; 277 | var soundSet = OMusicPlayer.prototype.getSoundSetForSoundFont( 278 | s.split("_").join(" "), sf + s + "-mp3/"); 279 | var blankPart = {soundSet: soundSet, notes: monkey.newMelody()}; 280 | var part = new OMGPart(undefined,blankPart,monkey.section); 281 | monkey.song.partAdded(part); 282 | }, 283 | function () { 284 | var type = monkey.getRandomElement(["Sine", "Square", "Triangle", "Sawtooth"]); 285 | var soundSet = {"url":"PRESET_OSC_" + type.toUpperCase(),"name": type + " Oscillator", 286 | "type":"SOUNDSET","octave":5,"lowNote":0,"highNote":108,"chromatic":true}; 287 | var blankPart = {soundSet: soundSet, notes: monkey.newMelody()}; 288 | var part = new OMGPart(undefined,blankPart,monkey.section); 289 | monkey.song.partAdded(part); 290 | } 291 | ]; 292 | }; 293 | 294 | OMGMonkey.prototype.newMelody = function () { 295 | var melody = this.getRandomElement(this.getNewMelodyFunctions())(); 296 | return melody; 297 | 298 | }; 299 | 300 | OMGMonkey.prototype.getNewMelodyFunctions = function () { 301 | var monkey = this; 302 | return [ 303 | function () { 304 | var beatsLeft = monkey.song.data.beatParams.beats * 305 | monkey.song.data.beatParams.measures; 306 | var notes = []; 307 | var note; 308 | var nextNoteNumber = Math.floor(Math.random() * 20) - 10; 309 | while (beatsLeft > 0) { 310 | note = monkey.getRandomNote(); 311 | note.note = nextNoteNumber; 312 | note.beats = Math.min(note.beats, beatsLeft); 313 | notes.push(note); 314 | beatsLeft -= note.beats; 315 | nextNoteNumber = monkey.getRandomNoteNumber(nextNoteNumber); 316 | } 317 | return notes; 318 | } 319 | ]; 320 | }; 321 | 322 | OMGMonkey.prototype.getRandomBeats = function () { 323 | var n = (Math.floor(Math.random() * 324 | this.song.data.beatParams.subbeats) + 1) / 325 | this.song.data.beatParams.subbeats; 326 | if (Math.random() > 0.8) { 327 | n = n * this.song.data.beatParams.beats; 328 | } 329 | return n; 330 | }; 331 | 332 | OMGMonkey.prototype.getRandomNote = function () { 333 | return { 334 | rest: Math.random() > 0.7, 335 | beats: this.getRandomBeats() 336 | }; 337 | }; 338 | 339 | OMGMonkey.prototype.getRandomNoteNumber = function (n) { 340 | if (Math.random() < 0.2) { 341 | return n; 342 | } 343 | if (Math.random() < 0.1) { 344 | return Math.floor(Math.random() * 20) - 10; 345 | } 346 | var d; 347 | if (Math.random() > 0.6) { 348 | d = Math.floor(Math.random() * 10) - 5; 349 | } 350 | else { 351 | d = Math.floor(Math.random() * 5) - 2; 352 | } 353 | return n + d; 354 | }; 355 | 356 | OMGMonkey.prototype.getPartDataFunctions = function () { 357 | var monkey = this; 358 | return [ 359 | function () { 360 | monkey.forRandomRandomParts(part => { 361 | if (part.data.surface.url === "PRESET_VERTICAL") { 362 | monkey.getRandomElement(monkey.getMelodyFunctions(part))(); 363 | } 364 | else { 365 | monkey.getRandomElement(monkey.getSequencerFunctions(part))(); 366 | } 367 | }); 368 | } 369 | ]; 370 | }; 371 | 372 | OMGMonkey.prototype.getMelodyFunctions = function (part) { 373 | var monkey = this; 374 | return [ 375 | function () { 376 | part.data.notes = monkey.newMelody(); 377 | OMusicPlayer.prototype.rescale(part, 378 | monkey.song.data.keyParams, monkey.section.data.chordProgression[0]); 379 | }, 380 | function () { 381 | if (part.data.notes.length === 0) { 382 | part.data.notes = monkey.newMelody(); 383 | } 384 | else { 385 | var note = monkey.getRandomElement(part.data.notes); 386 | note.note += (Math.floor(Math.random() * 5) + 1) * 387 | Math.random() > 0.5 ? 1 : -1; 388 | } 389 | } 390 | ]; 391 | }; 392 | 393 | OMGMonkey.prototype.getSequencerFunctions = function (part) { 394 | var monkey = this; 395 | return [ 396 | function () { 397 | part.data.tracks.forEach(track => { 398 | for (var i = 0; i < track.data.length; i++) { 399 | if (Math.random() < 0.1) { 400 | track.data[i] = monkey.getRandomBeatStrength(); 401 | } 402 | } 403 | }); 404 | }, 405 | function () { 406 | var track = monkey.getRandomElement(part.data.tracks); 407 | for (var i = 0; i < track.data.length; i++) { 408 | if (Math.random() < 0.1) { 409 | track.data[i] = monkey.getRandomBeatStrength(); 410 | } 411 | } 412 | }, 413 | function () { 414 | part.data.tracks.forEach(track => { 415 | for (var i = 0; i < track.data.length; i++) { 416 | if (Math.random() < 0.1) { 417 | track.data[i] = 0; 418 | } 419 | } 420 | }); 421 | }, 422 | function () { 423 | var track = monkey.getRandomElement(part.data.tracks); 424 | if (Math.random() < 0.1) { 425 | track.data[i] = 0; 426 | } 427 | }, 428 | function () { 429 | var track = monkey.getRandomElement(part.data.tracks); 430 | for (var i = 0; i < track.data.length; i++) { 431 | track.data[i] = i % monkey.song.data.beatParams.subbeats == 0 ? 1 : 0; 432 | } 433 | }, 434 | function () { 435 | var track = monkey.getRandomElement(part.data.tracks); 436 | for (var i = 0; i < track.data.length; i++) { 437 | track.data[i] = i % (monkey.song.data.beatParams.subbeats / 2) == 0 ? 1 : 0; 438 | } 439 | }, 440 | function () { 441 | var track = monkey.getRandomElement(part.data.tracks); 442 | track.audioParams.mute = !track.audioParams.mute; 443 | } 444 | ]; 445 | }; 446 | 447 | OMGMonkey.prototype.getRandomBeatStrength = function () { 448 | return this.getRandomElement([1, 0.5, 0.25]); 449 | }; 450 | 451 | OMGMonkey.prototype.newSong = function () { 452 | this.section = new OMGSection(); 453 | this.song = this.section.song; 454 | var partCount = Math.floor(Math.random() * 5) + 1; 455 | var part; 456 | for (var i = 0; i < partCount; i++) { 457 | this.getRandomElement(this.getNewPartFunctions())(); 458 | part = this.section.parts[this.section.parts.length - 1]; 459 | part.data.audioParams.gain = Math.min(Math.max(Math.random(), 0.05), 460 | part.data.soundSet.url.startsWith("PRESET_OSC") ? 0.5 : 0.9); 461 | part.data.audioParams.pan = Math.random() * Math.random() > 0.5 ? 1 : -1; 462 | } 463 | this.getRandomElement(this.getChordsFunctions())(); 464 | this.getRandomElement(this.getKeyFunctions())(); 465 | this.getRandomElement(this.getTempoFunctions())(); 466 | return this.song; 467 | }; 468 | -------------------------------------------------------------------------------- /www/js/omusic-embed-draw.js: -------------------------------------------------------------------------------- 1 | import NoteDrawer from "./NoteDrawer.js" 2 | 3 | export default function OMGEmbeddedViewerMusicDrawer() { 4 | this.noteDrawer = new NoteDrawer() 5 | } 6 | 7 | OMGEmbeddedViewerMusicDrawer.prototype.drawCanvas = function (data, canvas, subbeatsToDraw, performanceData) { 8 | 9 | this.noteDrawer.onReady(() => { 10 | this.song = data 11 | this.context = canvas.getContext("2d"); 12 | canvas.width = canvas.clientWidth 13 | canvas.height = canvas.clientHeight 14 | this.width = canvas.width 15 | this.height = canvas.height 16 | this.canvas = canvas 17 | 18 | this.subbeatsToDraw = subbeatsToDraw 19 | this.performanceData = performanceData 20 | 21 | this.getDrawingData(); 22 | this.draw(); 23 | }) 24 | }; 25 | 26 | OMGEmbeddedViewerMusicDrawer.prototype.getDrawingData = function () { 27 | var viewer = this; 28 | var beatParams = this.song.beatParams; 29 | this.beatParams = beatParams; 30 | this.totalSubbeats = beatParams.subbeats * beatParams.beats * beatParams.measures; 31 | this.measuresToDraw = 0 32 | 33 | this.drawingData = {sections: []}; 34 | let sections = this.performanceData || this.song.sections 35 | for (var sectionName in sections) { 36 | var section = sections[sectionName] 37 | var chordedData = []; 38 | chordedData.sectionName = section.name; 39 | var chordProgression = section.chordProgression || [0] 40 | for (var ic = 0; ic < chordProgression.length; ic++) { 41 | //todo section.rescale(section.chordProgression[ic]) 42 | 43 | var sectionData = viewer.getSectionDrawingData(section); 44 | chordedData.push(sectionData); 45 | } 46 | viewer.drawingData.sections.push(chordedData); 47 | this.measuresToDraw += section.measures || 1 48 | } 49 | 50 | // todo figure out this arrangement thing, will need to recount measuresToDraw 51 | 52 | var arrangement = []; 53 | if (!this.performanceData && this.song.arrangement && this.song.arrangement.length > 0) { 54 | for (var ia = 0; ia < this.song.arrangement.length; ia++) { 55 | for (var isection = 0; isection < this.drawingData.sections.length; isection++) { 56 | if (this.drawingData.sections[isection].sectionName === this.song.arrangement[ia].name) { 57 | for (var ir = -1; ir < (this.song.arrangement[ia].repeat || 0); ir++) { 58 | for (var ic = 0; ic < this.drawingData.sections[isection].length; ic++) { 59 | arrangement.push(this.drawingData.sections[isection][ic]); 60 | } 61 | } 62 | } 63 | } 64 | } 65 | } 66 | else { 67 | for (var isection = 0; isection < this.drawingData.sections.length; isection++) { 68 | for (var ic = 0; ic < this.drawingData.sections[isection].length; ic++) { 69 | arrangement.push(this.drawingData.sections[isection][ic]); 70 | } 71 | } 72 | } 73 | this.drawingData.sections = arrangement; 74 | 75 | if (this.drawingData.sections.length > 0) { 76 | this.measureLength = Math.max(40, this.width / this.measuresToDraw); 77 | this.subbeatLength = this.measureLength / this.totalSubbeats; 78 | } 79 | 80 | if (this.subbeatsToDraw) { 81 | this.subbeatLength = this.width / this.subbeatsToDraw 82 | } 83 | }; 84 | 85 | OMGEmbeddedViewerMusicDrawer.prototype.getSectionDrawingData = function (section) { 86 | var viewer = this; 87 | var measures = section.measures || 1 88 | var sectionData = {tracks: [], notes: [], section: section, measures: measures}; 89 | for (var partName in section.parts) { 90 | var part = section.parts[partName] 91 | var headPart = this.findHeadPart(part.name) 92 | if (headPart.audioParams.mute) { 93 | continue; 94 | } 95 | 96 | if (headPart.surface.url === "PRESET_SEQUENCER") { 97 | for (var i = 0; i < part.tracks.length; i++) { 98 | if (part.tracks[i].audioParams.mute) { 99 | continue; 100 | } 101 | for (var j = 0; j < viewer.totalSubbeats * measures; j++) { 102 | if (part.tracks[i].data[j]) { 103 | sectionData.tracks.push(part.tracks[i]); 104 | break; 105 | } 106 | } 107 | } 108 | } 109 | else { 110 | if (part.notes && part.notes.length > 0) { 111 | sectionData.notes.push(JSON.parse(JSON.stringify(part.notes))); 112 | } 113 | } 114 | } 115 | 116 | if (sectionData.tracks.length > 0) { 117 | if (sectionData.notes.length === 0) { 118 | sectionData.trackHeight = this.height / sectionData.tracks.length; 119 | } 120 | else { 121 | sectionData.trackHeight = this.height / 2 / sectionData.tracks.length; 122 | sectionData.notesHeight = this.height / 2; 123 | } 124 | } 125 | else { 126 | sectionData.notesHeight = this.height; 127 | } 128 | return sectionData; 129 | } 130 | 131 | OMGEmbeddedViewerMusicDrawer.prototype.draw = function () { 132 | if (this.predraw) { 133 | this.predraw(); 134 | } 135 | 136 | this.canvas.width = this.canvas.width 137 | var subbeatsDrawn = 0 138 | 139 | var noteSize = this.subbeatLength > 30 ? 30 : 20 140 | var usedBeats; 141 | var marginX, marginY; 142 | var value; 143 | var section 144 | var subbeatsToDraw 145 | for (var isection = 0; isection < this.drawingData.sections.length; isection++) { 146 | section = this.drawingData.sections[isection] 147 | if (!this.subbeatsToDraw) { 148 | subbeatsToDraw = this.totalSubbeats * section.measures 149 | } 150 | else { 151 | subbeatsToDraw = (this.subbeatsToDraw - subbeatsDrawn > this.totalSubbeats) ? this.totalSubbeats : (this.subbeatsToDraw - subbeatsDrawn) 152 | } 153 | 154 | for (var itrack = section.tracks.length - 1; itrack >= 0 ; itrack--) { 155 | for (var i = 0; i < subbeatsToDraw; i++) { 156 | value = section.tracks[itrack].data[i]; 157 | marginX = 1; 158 | marginY = this.trackHeight > 5 ? 1 : 0; 159 | if (typeof value === "number" && value > 0 && value < 1) { 160 | this.context.fillStyle = i % this.beatParams.beats == 0 ? "#DDDDDD" : "white"; 161 | this.context.fillRect((i + subbeatsDrawn) * this.subbeatLength + marginX, 162 | this.height - itrack * section.trackHeight - marginY, 163 | this.subbeatLength - marginX * 2, -1 * section.trackHeight + marginY * 2); 164 | marginY = (section.trackHeight - section.trackHeight * value) / 3; 165 | marginX = (this.subbeatLength - this.subbeatLength * value) / 3; 166 | } 167 | this.context.fillStyle = value ? "black" : 168 | (i % this.beatParams.beats == 0 ? "#DDDDDD" : "white"); 169 | this.context.fillRect((i + subbeatsDrawn) * this.subbeatLength + marginX, 170 | this.height - itrack * section.trackHeight - marginY, 171 | this.subbeatLength - marginX * 2, -1 * section.trackHeight + marginY * 2); 172 | } 173 | } 174 | this.context.fillStyle = "black"; 175 | this.context.font = noteSize + "pt serif"; 176 | var y, x, note 177 | for (var inotes = 0; inotes < section.notes.length; inotes++) { 178 | usedBeats = 0; 179 | for (var inote = 0; inote < section.notes[inotes].length; inote++) { 180 | note = section.notes[inotes][inote].scaledNote 181 | x = subbeatsDrawn * this.subbeatLength + this.subbeatLength * usedBeats * this.beatParams.subbeats 182 | 183 | y = (109 - note) / 109 * section.notesHeight 184 | 185 | this.noteDrawer.drawNote(section.notes[inotes][inote], this.context, x, y) 186 | 187 | usedBeats += section.notes[inotes][inote].beats; 188 | if (usedBeats * this.beatParams.subbeats >= this.totalSubbeats * section.measures) { 189 | break; 190 | } 191 | } 192 | } 193 | 194 | if (isection > 0 || subbeatsDrawn > 0) { 195 | this.context.beginPath(); 196 | this.context.lineWidth = 1; 197 | this.context.strokeStyle = "black"; 198 | this.context.moveTo(subbeatsDrawn * this.subbeatLength, 0); 199 | this.context.lineTo(subbeatsDrawn * this.subbeatLength, this.height); 200 | this.context.stroke(); 201 | } 202 | 203 | this.context.beginPath(); 204 | this.context.lineWidth = 1; 205 | this.context.strokeStyle = "#AAAAAA"; 206 | for (var imeasure = 1; imeasure < this.beatParams.measures; imeasure++) { 207 | this.context.moveTo(subbeatsDrawn * this.subbeatLength + imeasure * this.measureLength, 0); 208 | this.context.lineTo(subbeatsDrawn * this.subbeatLength + imeasure * this.measureLength, this.height); 209 | } 210 | this.context.stroke(); 211 | 212 | subbeatsDrawn += this.beatParams.subbeats * this.beatParams.beats * this.beatParams.measures 213 | if (this.subbeatsToDraw && subbeatsDrawn >= this.subbeatsToDraw) { 214 | return 215 | } 216 | if (this.subbeatsToDraw && isection === this.drawingData.sections.length - 1 && subbeatsDrawn < this.subbeatsToDraw) { 217 | isection = -1 218 | } 219 | } 220 | }; 221 | 222 | 223 | // the above does the whole OMGSong, and now I need just a single part 224 | // instead of refactoring, I'm doubling up 225 | 226 | 227 | OMGEmbeddedViewerMusicDrawer.prototype.drawPartCanvas = function (partData, canvas, surface, beatParams, measures) { 228 | 229 | canvas.width = canvas.clientWidth 230 | canvas.height = canvas.clientHeight 231 | 232 | if (surface === "PRESET_SEQUENCER") { 233 | this.drawPartBeats(partData, canvas, beatParams, measures, 0, 0, canvas.width, canvas.height) 234 | } 235 | else { 236 | this.drawPartNotes(partData, canvas, beatParams, measures, 0, 0, canvas.width, canvas.height) 237 | } 238 | } 239 | 240 | OMGEmbeddedViewerMusicDrawer.prototype.drawPartNotes = function (part, canvas, beatParams, measures, x, y, w, h) { 241 | 242 | var y, x, note 243 | var usedBeats = 0; 244 | 245 | var subbeatsDrawn = 0 246 | var subbeatsToDraw = beatParams.subbeats * beatParams.beats * measures 247 | var subbeatLength = canvas.width / subbeatsToDraw 248 | var noteSize = subbeatLength > 30 ? 30 : 20 249 | 250 | var context = canvas.getContext("2d"); 251 | 252 | context.fillStyle = "#DDDDDD" 253 | context.fillRect(0, 0, canvas.width, canvas.height) 254 | 255 | context.fillStyle = "black"; 256 | context.font = noteSize + "pt serif"; 257 | 258 | for (var note of part.notes) { 259 | x = subbeatsDrawn * subbeatLength + subbeatLength * usedBeats * beatParams.subbeats 260 | 261 | y = (109 - note.note) / 109 * canvas.height 262 | 263 | this.noteDrawer.drawNote(note, context, x, y) 264 | 265 | usedBeats += note.beats; 266 | if (usedBeats * beatParams.subbeats >= subbeatsToDraw) { 267 | break; 268 | } 269 | } 270 | } 271 | 272 | OMGEmbeddedViewerMusicDrawer.prototype.drawPartBeats = function (part, canvas, beatParams, measures, x, y, w, h) { 273 | var context = canvas.getContext("2d"); 274 | 275 | var tracks = part.tracks 276 | var subbeatsDrawn = 0 277 | var subbeatsToDraw = beatParams.subbeats * beatParams.beats * measures 278 | var subbeatLength = canvas.width / subbeatsToDraw 279 | var height = canvas.height / tracks.length 280 | 281 | for (var itrack = 0; itrack < tracks.length; itrack++) { 282 | for (var i = 0; i < subbeatsToDraw; i++) { 283 | var value = tracks[itrack].data[i]; 284 | var marginX = 1; 285 | var marginY = 1 //this.trackHeight > 5 ? 1 : 0; 286 | if (typeof value === "number" && value > 0 && value < 1) { 287 | context.fillStyle = i % beatParams.beats == 0 ? "#DDDDDD" : "white"; 288 | context.fillRect((i + subbeatsDrawn) * subbeatLength + marginX, 289 | itrack * height - marginY, 290 | subbeatLength - marginX * 2, -1 * height + marginY * 2); 291 | marginY = (height - height * value) / 3; 292 | marginX = (subbeatLength - subbeatLength * value) / 3; 293 | } 294 | context.fillStyle = value ? "black" : 295 | (i % beatParams.beats == 0 ? "#DDDDDD" : "white"); 296 | context.fillRect((i + subbeatsDrawn) * subbeatLength + marginX, 297 | itrack * height - marginY, 298 | subbeatLength - marginX * 2, height + marginY * 2); 299 | } 300 | } 301 | } 302 | 303 | OMGEmbeddedViewerMusicDrawer.prototype.findHeadPart = function (partName) { 304 | for (var i = 0; i < this.song.parts.length; i++) { 305 | if (partName === this.song.parts[i].name) { 306 | return this.song.parts[i] 307 | } 308 | } 309 | } -------------------------------------------------------------------------------- /www/js/omusic-embed.js: -------------------------------------------------------------------------------- 1 | function OMGEmbeddedViewerMusic(viewer) { 2 | this.canvas = document.createElement("canvas") 3 | this.canvas.className = "omg-viewer-canvas" 4 | 5 | if (viewer.params && viewer.params.maxHeight) { 6 | this.canvas.style.maxHeight = viewer.params.maxHeight + "px" 7 | } 8 | 9 | viewer.embedDiv.appendChild(this.canvas) 10 | 11 | this.canvas.width = viewer.embedDiv.clientWidth 12 | this.canvas.height = viewer.embedDiv.clientHeight 13 | 14 | this.data = viewer.data 15 | this.viewer = viewer 16 | 17 | import("/apps/music/js/omusic-embed-draw.js").then(o => { 18 | var OMGEmbeddedViewerMusicDrawer = o.default 19 | 20 | this.drawer = new OMGEmbeddedViewerMusicDrawer() 21 | this.drawer.drawCanvas(this.data, this.canvas) 22 | this.drawingData = this.drawer.drawingData 23 | 24 | this.makePlayButton() 25 | this.makeBeatMarker() 26 | }) 27 | } 28 | 29 | if (typeof omg === "object" && omg.types && omg.types["SONG"]) { 30 | omg.types["SONG"].embedClass = OMGEmbeddedViewerMusic 31 | omg.types["PART"].embedClass = OMGEmbeddedViewerMusic 32 | } 33 | 34 | OMGEmbeddedViewerMusic.prototype.makePlayButton = function () { 35 | 36 | this.playButton = document.createElement("div") 37 | this.playButton.className = "omg-viewer-play-button" 38 | 39 | var img = document.createElement("img") 40 | img.src = "/apps/music/img/play-button.svg" 41 | img.style.height = this.canvas.clientHeight / 2 + "px" 42 | img.style.marginLeft = this.canvas.clientWidth / 2 - this.canvas.clientHeight / 4 + "px" 43 | img.style.marginTop = this.canvas.clientHeight / 4 + "px" 44 | img.style.cursor = "pointer" 45 | this.playButtonImg = img 46 | this.playButton.appendChild(img) 47 | 48 | this.playButton.style.opacity = "0.7" 49 | img.onmouseenter = e => this.playButton.style.opacity = "1" 50 | img.onmouseleave = e => this.playButton.style.opacity = "0.7" 51 | 52 | this.viewer.embedDiv.appendChild(this.playButton) 53 | 54 | this.playButtonImg.onclick = e => this.playButtonClick() 55 | } 56 | 57 | OMGEmbeddedViewerMusic.prototype.playButtonClick = async function (data) { 58 | 59 | let playcountUpdate = () => { 60 | if (!this.playButtonHasBeenClicked) { 61 | this.playButtonHasBeenClicked = true; 62 | omg.server.postHTTP("/playcount", {id: this.data.id}); 63 | } 64 | } 65 | 66 | if (!this.player) { 67 | this.playButton.classList.add("loader") 68 | var OMusicContext = await import("/apps/music/js/omusic.js") 69 | //var OMusicContext = await import("./omusic.js") 70 | this.musicContext = new OMusicContext.default() 71 | 72 | var {player, song} = await this.musicContext.load(this.data) 73 | 74 | this.player = player 75 | this.song = song 76 | 77 | this.setPlayer(this.player) 78 | } 79 | 80 | if (this.player.playing) { 81 | this.player.stop() 82 | this.beatMarker.style.display = "none" 83 | this.playButtonImg.src = "/apps/music/img/play-button.svg" 84 | } 85 | else { 86 | this.player.play() 87 | this.beatMarker.style.display = "block" 88 | playcountUpdate() 89 | } 90 | } 91 | 92 | OMGEmbeddedViewerMusic.prototype.setPlayer = function (player) { 93 | this.player = player 94 | this.player.onPlayListeners.push(() => { 95 | this.playButton.classList.remove("loader") 96 | this.playButtonImg.src = "/apps/music/img/stop-button.svg" 97 | }) 98 | 99 | this.player.onBeatPlayedListeners.push(this.onBeatPlayedListener) 100 | this.player.onloop = () => this.onloop(); 101 | 102 | } 103 | 104 | OMGEmbeddedViewerMusic.prototype.makeBeatMarker = function () { 105 | 106 | //var pxPerBeat = (this.canvas.clientWidth) / (this.drawer.totalSubbeats * this.drawingData.sections.length); 107 | //var beatsInSection = this.song.data.beatParams.measures * this.song.data.beatParams.beats * this.song.data.beatParams.subbeats; 108 | this.subbeatsPlayed = 0; 109 | this.beatMarker = document.createElement("div") 110 | this.beatMarker.className = "beat-marker" 111 | this.beatMarker.style.width = this.drawer.subbeatLength + "px"; 112 | this.beatMarker.style.display = "none" 113 | this.viewer.embedDiv.appendChild(this.beatMarker) 114 | this.onBeatPlayedListener = (isubbeat, isection) => { 115 | this.beatMarker.style.left = this.drawer.subbeatLength * this.subbeatsPlayed + "px"; 116 | if (isubbeat > -1) { 117 | this.subbeatsPlayed++ 118 | } 119 | else { 120 | this.subbeatsPlayed = 0 121 | } 122 | }; 123 | }; 124 | 125 | OMGEmbeddedViewerMusic.prototype.onloop = function () { 126 | this.subbeatsPlayed = 0 127 | } 128 | -------------------------------------------------------------------------------- /www/js/piano_surface.js: -------------------------------------------------------------------------------- 1 | function PianoSurface(div) { 2 | var blackKeys = [1, 3, 6, 8, 10] 3 | var noteNames = ["C", "C#", "D", "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B"] 4 | 5 | var keys = [] 6 | var key 7 | for (var i = 0; i < 128; i++) { 8 | 9 | var ii = i % 12 10 | 11 | key = { 12 | note: i, 13 | white: blackKeys.indexOf(ii) === -1, 14 | name: noteNames[ii] 15 | } 16 | 17 | keys.push(key) 18 | 19 | } 20 | 21 | this.keys = keys 22 | this.start = 24 23 | this.end = 96 24 | 25 | this.whiteKeyCount = 0 26 | for (i = this.start; i <= this.end; i++) { 27 | if (keys[i].white) { 28 | this.whiteKeyCount++ 29 | } 30 | } 31 | 32 | this.blackKeyLength = 0.5 33 | this.showNoteNames = true 34 | 35 | this.setupCanvas(div) 36 | this.setupMultiTouch(this.ctx.canvas, this) 37 | this.setupKeyboard() 38 | 39 | } 40 | 41 | PianoSurface.prototype.setupCanvas = function (div) { 42 | var canvasBack = document.createElement("canvas") 43 | var canvas = document.createElement("canvas") 44 | 45 | canvasBack.style.width = "100%" 46 | canvasBack.style.height = "100%" 47 | canvas.style.width = "100%" 48 | canvas.style.height = "100%" 49 | canvas.style.position = "absolute" 50 | canvas.style.top = "0px" 51 | canvas.style.left = "0px" 52 | canvasBack.style.position = "absolute" 53 | canvasBack.style.top = "0px" 54 | canvasBack.style.left = "0px" 55 | 56 | div.appendChild(canvasBack) 57 | div.appendChild(canvas) 58 | 59 | var ctx = canvasBack.getContext("2d") 60 | this.ctx = canvas.getContext("2d") 61 | 62 | 63 | ctx.canvas.width = ctx.canvas.clientWidth 64 | ctx.canvas.height = ctx.canvas.clientHeight 65 | this.ctx.canvas.width = ctx.canvas.clientWidth 66 | this.ctx.canvas.height = ctx.canvas.clientHeight 67 | console.log(this.ctx.canvas.clientWidth, ctx.canvas.clientWidth) 68 | ctx.fillStyle = "white" 69 | ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height) 70 | ctx.strokeStyle = "black" 71 | ctx.fillStyle = "black" 72 | ctx.textAlign = "center" 73 | this.whiteKeys = [] 74 | this.whiteKeyWidth = ctx.canvas.width / this.whiteKeyCount 75 | for (this._di = this.start; this._di <= this.end; this._di++) { 76 | 77 | if (this.keys[this._di].white) { 78 | this.keys[this._di].x = this.whiteKeys.length * this.whiteKeyWidth 79 | ctx.strokeRect(this.keys[this._di].x, 0, this.whiteKeyWidth, ctx.canvas.height) 80 | this.whiteKeys.push(this.keys[this._di]) 81 | 82 | if (this.showNoteNames) { 83 | ctx.fillText(this.keys[this._di].name, this.keys[this._di].x + this.whiteKeyWidth / 2, ctx.canvas.height * 0.8) 84 | } 85 | } 86 | else { 87 | this.keys[this._di].x = 2 + this.whiteKeys.length * this.whiteKeyWidth - this.whiteKeyWidth / 2 88 | ctx.fillRect(this.keys[this._di].x, 0, 89 | this.whiteKeyWidth - 2, ctx.canvas.height * this.blackKeyLength) 90 | } 91 | } 92 | console.log(this.ctx.canvas.clientWidth, ctx.canvas.clientWidth) 93 | 94 | } 95 | 96 | PianoSurface.prototype.drawPressed = function (pressed) { 97 | this.ctx.canvas.width = this.ctx.canvas.clientWidth 98 | this.ctx.canvas.height = this.ctx.canvas.clientHeight 99 | this.ctx.fillStyle = "green" 100 | this.ctx.strokeStyle = "black" 101 | this.ctx.textAlign = "center" 102 | this.pressedKeyOffset = 4 103 | for (this._di = 0; this._di < pressed.length; this._di++) { 104 | 105 | this._dk = this.keys[pressed[this._di]] 106 | if (this._dk.white) { 107 | this.ctx.fillRect(this._dk.x + this.pressedKeyOffset, 108 | this.ctx.canvas.height * this.blackKeyLength + this.pressedKeyOffset, 109 | this.whiteKeyWidth - this.pressedKeyOffset * 2, 110 | this.ctx.canvas.height * (1 - this.blackKeyLength) - this.pressedKeyOffset * 2) 111 | //this.ctx.strokeRect(this._dk.x, 0, this.whiteKeyWidth, this.ctx.canvas.height) 112 | 113 | if (this.showNoteNames) { 114 | this.ctx.fillStyle = "white" 115 | this.ctx.fillText(this._dk.name, this._dk.x + this.whiteKeyWidth / 2, this.ctx.canvas.height * 0.8) 116 | this.ctx.fillStyle = "green" 117 | } 118 | } 119 | else { 120 | this.ctx.fillRect(this._dk.x + this.pressedKeyOffset, 121 | this.pressedKeyOffset, 122 | this.whiteKeyWidth - 2 - this.pressedKeyOffset * 2, 123 | this.ctx.canvas.height * this.blackKeyLength - 2 - this.pressedKeyOffset * 2) 124 | //this.ctx.strokeRect(this._dk.x, 0, this.whiteKeyWidth - 2, this.ctx.canvas.height * this.blackKeyLength) 125 | } 126 | } 127 | } 128 | 129 | PianoSurface.prototype.noteOn = function () { 130 | //this.drawPressed(this.touches) 131 | } 132 | PianoSurface.prototype.noteOff = function () { 133 | //this.drawPressed(this.touches) 134 | } 135 | 136 | PianoSurface.prototype.setupEvents = function (noteOn, noteOff) { 137 | this.noteOn = noteOn 138 | this.noteOff = noteOff 139 | } 140 | 141 | PianoSurface.prototype.ondown = function (touch) { 142 | touch.key = this.keyHitTest(touch) 143 | this.noteOn(this.keys[touch.key]) 144 | } 145 | PianoSurface.prototype.onmove = function (touch) { 146 | var key = this.keyHitTest(touch) 147 | if (key !== touch.key) { 148 | this.noteOff(this.keys[touch.key]) 149 | touch.key = key 150 | this.noteOn(this.keys[key]) 151 | } 152 | 153 | } 154 | PianoSurface.prototype.onup = function (touch) { 155 | this.noteOff(this.keys[touch.key]) 156 | } 157 | 158 | PianoSurface.prototype.keyHitTest = function (touch) { 159 | var key = this.whiteKeys[Math.floor(touch.x / this.whiteKeyWidth)].note 160 | if (touch.y / this.ctx.canvas.height < this.blackKeyLength && touch.x % this.whiteKeyWidth < this.whiteKeyWidth / 2 && !this.keys[key - 1].white) { 161 | key-- 162 | } 163 | else if (touch.y / this.ctx.canvas.height < this.blackKeyLength && touch.x % this.whiteKeyWidth > this.whiteKeyWidth / 2 && !this.keys[key + 1].white) { 164 | key++ 165 | } 166 | return key 167 | } 168 | 169 | //todo move this to omg.ui? 170 | PianoSurface.prototype.setupMultiTouch = function (div, handler) { 171 | handler.touches = [] 172 | var removeTouch = touch => { 173 | var touchIndex = handler.touches.indexOf(touch); 174 | if (touchIndex > -1) { 175 | handler.touches.splice(touchIndex, 1); 176 | } 177 | } 178 | 179 | handler.redoOffsets = true 180 | var updateOffsets = function () { 181 | handler.offsets = omg.ui.totalOffsets(div) 182 | handler.redoOffsets = false 183 | } 184 | 185 | div.onmousedown = e => { 186 | if (handler.readOnly) return; 187 | e.preventDefault(); 188 | if (handler.redoOffsets) { 189 | updateOffsets() 190 | } 191 | 192 | handler.mouseTouch = {x: e.clientX - handler.offsets.left, 193 | y: e.clientY + omg.ui.getScrollTop() - handler.offsets.top, 194 | identifier: "mouse" 195 | }; 196 | handler.touches.push(handler.mouseTouch) 197 | handler.ondown(handler.mouseTouch); 198 | }; 199 | 200 | div.onmousemove = e => { 201 | if (handler.readOnly) return; 202 | e.preventDefault(); 203 | if (handler.redoOffsets) { 204 | updateOffsets() 205 | } 206 | 207 | if (handler.mouseTouch) { 208 | handler.mouseTouch.lastX = handler.mouseTouch.x; 209 | handler.mouseTouch.lastY = handler.mouseTouch.y; 210 | handler.mouseTouch.x = e.clientX - handler.offsets.left; 211 | handler.mouseTouch.y = e.clientY + omg.ui.getScrollTop() - handler.offsets.top; 212 | handler.onmove(handler.mouseTouch); 213 | } 214 | else { 215 | //todo onhover? 216 | } 217 | }; 218 | 219 | div.onmouseout = e => { 220 | if (handler.readOnly) return; 221 | e.preventDefault(); 222 | if (handler.mouseTouch) { 223 | handler.onup(handler.mouseTouch); //todo oncancel? 224 | handler.mouseTouch = null; 225 | removeTouch(handler.mouseTouch) 226 | } 227 | }; 228 | 229 | div.onmouseup = e => { 230 | if (handler.readOnly) return; 231 | e.preventDefault(); 232 | if (handler.mouseTouch) { 233 | handler.onup(handler.mouseTouch); 234 | handler.mouseTouch = null; 235 | removeTouch(handler.mouseTouch) 236 | } 237 | }; 238 | 239 | div.addEventListener("touchstart", e => { 240 | if (handler.readOnly) return; 241 | e.preventDefault(); 242 | if (handler.redoOffsets) { 243 | updateOffsets(); 244 | } 245 | 246 | for (var i = 0; i < e.changedTouches.length; i++) { 247 | var touch = {x: e.changedTouches[i].pageX - handler.offsets.left, 248 | y: e.changedTouches[i].pageY + omg.ui.getScrollTop() - handler.offsets.top, 249 | identifier: e.changedTouches[i].identifier} 250 | handler.ondown(touch); 251 | handler.touches.push(touch) 252 | } 253 | }); 254 | 255 | div.addEventListener("touchmove", e => { 256 | if (handler.readOnly) return; 257 | e.preventDefault(); 258 | 259 | for (var i = 0; i < e.changedTouches.length; i++) { 260 | for (var j = 0; j < handler.touches.length; j++) { 261 | var touch = handler.touches[j]; 262 | if (e.changedTouches[i].identifier === touch.identifier) { 263 | touch.lastX = touch.x; 264 | touch.lastY = touch.y; 265 | touch.x = e.changedTouches[i].pageX - handler.offsets.left; 266 | touch.y = e.changedTouches[i].pageY + omg.ui.getScrollTop() - handler.offsets.top; 267 | handler.onmove(touch); 268 | break; 269 | } 270 | } 271 | } 272 | }); 273 | 274 | div.addEventListener("touchend", e => { 275 | if (handler.readOnly) return; 276 | e.preventDefault(); 277 | for (var i = 0; i < e.changedTouches.length; i++) { 278 | for (var j = 0; j < handler.touches.length; j++) {; 279 | if (e.changedTouches[i].identifier === handler.touches[j].identifier) { 280 | handler.onup(handler.touches[j]); 281 | removeTouch(handler.touches[j]) 282 | break; 283 | } 284 | } 285 | } 286 | }); 287 | 288 | } 289 | 290 | PianoSurface.prototype.setupKeyboard = function () { 291 | 292 | var keyMap = { 293 | "a": 48, "s": 50, "d": 52, "f": 53, "g": 55, "h": 57, "j": 59, "k": 60, "l": 62, ";": 64, 294 | "q": 47, "w": 49, "e": 51, "r": 52, "t": 54, "y": 56, "u": 58, "i": 60, "o": 61, "p": 63, 295 | "z": 36, "x": 38, "c": 40, "v": 41, "b": 43, "n": 45, "m": 47 296 | } 297 | 298 | var keys = {} 299 | 300 | document.body.onkeydown = e => { 301 | let note = keyMap[e.key] + 12 302 | if (note && !keys[e.key]) { 303 | this.noteOn(note, 66) 304 | keys[e.key] = true 305 | } 306 | } 307 | document.body.onkeyup = e => { 308 | let note = keyMap[e.key] + 12 309 | if (note) { 310 | this.noteOff(note) 311 | keys[e.key] = false 312 | } 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /www/js/remote.js: -------------------------------------------------------------------------------- 1 | var url = window.location.origin.replace("http:", "https:"); 2 | var socket = io(url + "/omg-live"); 3 | var canvas = document.getElementById("main-canvas"); 4 | var ctx = canvas.getContext("2d"); 5 | 6 | var lastX = -1; 7 | var lastY = -1; 8 | var isTouching = false; 9 | 10 | var colors = [ "#FFFFFF", "#FF0000", "#FFFF00", "#00FF00", "#0000FF", 11 | "#FF8000", "#9E9E9E", "#00FFFF", "#800080", "#632DFF", "#63FF08" ]; 12 | 13 | var shapes = [ 14 | {"shape": "circle", "draw": function (x, y, fill) { 15 | ctx.lineWidth = 5; 16 | ctx.beginPath(); 17 | ctx.arc(x, y, 20, 0, 2 * Math.PI); 18 | ctx.stroke(); 19 | }}, 20 | {"shape": "fill_circle", "draw": function (x, y, fill) { 21 | ctx.beginPath(); 22 | ctx.arc(x, y, 20, 0, 2 * Math.PI); 23 | ctx.fill(); 24 | }}, 25 | {"shape": "square", "draw": function (x, y, fill) { 26 | ctx.lineWidth = 5; 27 | ctx.beginPath(); 28 | ctx.moveTo(x - 20, y - 20); 29 | ctx.lineTo(x + 20, y - 20); 30 | ctx.lineTo(x + 20, y + 20); 31 | ctx.lineTo(x - 20, y + 20); 32 | ctx.closePath(); 33 | ctx.stroke(); 34 | }}, 35 | {"shape": "fill_square", "draw": function (x, y, fill) { 36 | ctx.beginPath(); 37 | ctx.moveTo(x - 20, y - 20); 38 | ctx.lineTo(x + 20, y - 20); 39 | ctx.lineTo(x + 20, y + 20); 40 | ctx.lineTo(x - 20, y + 20); 41 | ctx.closePath(); 42 | ctx.fill(); 43 | }} 44 | ]; 45 | 46 | var colorI = Math.floor(Math.random() * colors.length); 47 | var shapeI = Math.floor(Math.random() * shapes.length); 48 | 49 | if (window.location.search) { 50 | colorI = parseInt(window.location.search.substring(1)); 51 | } 52 | 53 | var color = colors[colorI]; 54 | var shape = shapes[shapeI].shape; 55 | var drawShape = shapes[shapeI].draw; 56 | var username = Math.random().toString(36).substr(2); 57 | 58 | 59 | window.onload = function () { 60 | setupCanvas(); 61 | }; 62 | 63 | window.onresize = function () { 64 | setupCanvas(); 65 | }; 66 | 67 | var setupCanvas = function () { 68 | 69 | var height = window.innerHeight; 70 | var width = window.innerWidth; 71 | canvas.clientWidth = width; 72 | canvas.clientHeight = height; 73 | canvas.width = canvas.clientWidth; 74 | canvas.height = canvas.clientHeight; 75 | 76 | ctx.font = "5em Helvetica"; 77 | ctx.textAlign = "center"; 78 | ctx.textBaseline = "middle"; 79 | 80 | drawCanvas(canvas.width / 2, canvas.height / 2); 81 | }; 82 | 83 | 84 | var drawCanvas = function (x, y) { 85 | 86 | if (x > -1) { 87 | lastX = x; 88 | lastY = y; 89 | } 90 | 91 | ctx.fillStyle = "black"; 92 | ctx.fillRect(0, 0, canvas.width, canvas.height); 93 | 94 | ctx.font = "10pt Helvetica"; 95 | ctx.fillStyle = "white"; 96 | ctx.fillText("Move your shape around", canvas.width / 2, canvas.height / 4); 97 | 98 | ctx.fillStyle = color; 99 | ctx.strokeStyle = color; 100 | ctx.globalAlpha = isTouching ? 1.0 : 0.5; 101 | 102 | drawShape(lastX, lastY); 103 | 104 | //ctx.font = "5em Helvetica"; 105 | //ctx.fillText(letter, lastX, lastY); 106 | ctx.globalAlpha = 1.0; 107 | 108 | }; 109 | 110 | var canvasDownEvent = function (x, y) { 111 | isTouching = true; 112 | drawCanvas(x, y); 113 | 114 | socket.emit("basic", {room: room, 115 | 'user': username, 116 | 'x': x/canvas.width, 'y': y/canvas.height, 117 | 'color': color, 'shape': shape}); 118 | }; 119 | 120 | var canvasMoveEvent = function (x, y) { 121 | if (isTouching){ 122 | drawCanvas(x, y); 123 | socket.emit("basic", {room: room, 124 | 'user': username, 125 | 'x': x/canvas.width, 'y': y/canvas.height, 126 | 'color': color, 'shape': shape}); 127 | } 128 | }; 129 | 130 | var canvasEndEvent = function (x, y) { 131 | isTouching = false; 132 | //canvas.width = canvas.width; 133 | drawCanvas(-1); 134 | socket.emit("basic", {room: room, 'user': username, 'x':-1}); 135 | }; 136 | 137 | 138 | 139 | canvas.addEventListener("touchstart", function (e) { 140 | e.preventDefault(); 141 | canvasDownEvent(e.targetTouches[0].pageX, e.targetTouches[0].pageY); 142 | }); 143 | canvas.addEventListener("touchmove", function (e) { 144 | e.preventDefault(); 145 | canvasMoveEvent(e.targetTouches[0].pageX, e.targetTouches[0].pageY); 146 | }); 147 | canvas.addEventListener("touchend", function (e) { 148 | e.preventDefault(); 149 | canvasEndEvent(-1, -1); 150 | }); 151 | 152 | 153 | canvas.onmousedown = function (e) { 154 | canvasDownEvent(e.clientX, e.clientY); 155 | }; 156 | canvas.onmousemove = function (e) { 157 | canvasMoveEvent(e.clientX, e.clientY); 158 | }; 159 | canvas.onmouseup = function (e) { 160 | canvasEndEvent(e.clientX, e.clientY); 161 | }; 162 | 163 | -------------------------------------------------------------------------------- /www/js/soundset-embed.js: -------------------------------------------------------------------------------- 1 | function OMGEmbeddedViewerSOUNDSET(viewer) { 2 | this.viewer = viewer 3 | this.playChar = " ▶" 4 | this.stopChar = "◼" 5 | viewer.embedDiv.style.display = "flex" 6 | this.loadSoundSet(viewer.data, viewer.embedDiv) 7 | if (!OMGEmbeddedViewerSOUNDSET.omgviewerAddedCSS) { 8 | OMGEmbeddedViewerSOUNDSET.omgviewerAddedCSS = true 9 | var css = `.omg-soundset-audio-sample { 10 | text-align: center; 11 | padding:10px; 12 | margin:5px; 13 | border: 1px solid #808080; 14 | border-radius: 18px; 15 | width:12em; 16 | display:flex; 17 | background-color: #F0F0F0; 18 | cursor: pointer; 19 | } 20 | .omg-soundset-audio-sample a { 21 | text-decoration: none; 22 | color: black; 23 | } 24 | .omg-viewer-soundset-data { 25 | display: flex; 26 | flex-wrap: wrap; 27 | } 28 | .omg-soundset-audio-play { 29 | width: 20px; 30 | } 31 | .omg-soundset-audio-duration { 32 | margin-left: auto; 33 | }` 34 | 35 | var style = document.createElement("style") 36 | style.innerHTML = css 37 | document.body.appendChild(style) 38 | } 39 | } 40 | 41 | OMGEmbeddedViewerSOUNDSET.prototype.loadSoundSet = function (data, parentDiv) { 42 | this.audioSamples = [] 43 | this.viewer.divDataMap = new Map() 44 | data.data.forEach((item) => { 45 | var div = document.createElement("div") 46 | var audio = document.createElement("audio") 47 | var link = document.createElement("a") 48 | var playButton = document.createElement("div") 49 | var duration = document.createElement("div") 50 | playButton.className = "omg-soundset-audio-play" 51 | duration.className = "omg-soundset-audio-duration" 52 | playButton.innerHTML = this.playChar 53 | div.appendChild(playButton) 54 | link.src = (data.prefix || "") + item.url + (data.postfix || "") 55 | link.innerHTML = item.name 56 | div.appendChild(link) 57 | div.appendChild(duration) 58 | div.className = "omg-soundset-audio-sample" 59 | parentDiv.appendChild(div) 60 | 61 | var isPlaying = false 62 | audio.oncanplaythrough = () => { 63 | var min = Math.floor(audio.duration / 60) 64 | duration.innerHTML = min + ":" + Math.round(audio.duration - min * 60).toString().padStart(2, "0") 65 | } 66 | audio.src = link.src 67 | audio.onended = () => { 68 | playButton.innerHTML = this.playChar 69 | isPlaying = false 70 | } 71 | div.onclick = () => { 72 | if (isPlaying) { 73 | audio.pause() 74 | audio.currentTime = 0 75 | playButton.innerHTML = this.playChar 76 | } 77 | else{ 78 | audio.play() 79 | playButton.innerHTML = this.stopChar 80 | } 81 | isPlaying = !isPlaying 82 | } 83 | this.audioSamples.push({div: div, audio: audio}) 84 | this.viewer.divDataMap.set(div, {item: item, soundset: data, src: link.src}) 85 | }) 86 | } 87 | 88 | OMGEmbeddedViewer.prototype.playSoundSet = function () { 89 | if (this.playingSample) { 90 | this.playingSample.audio.removeEventListener("ended", this.playingSoundSetListener) 91 | this.playingSample.div.onclick() 92 | this.showStopped() 93 | return 94 | } 95 | 96 | this.showPlaying() 97 | var i = 0 98 | var playNext = () => { 99 | this.playingSoundSetListener = () => { 100 | this.audioSamples[i].audio.removeEventListener("ended", this.playingSoundSetListener) 101 | i++ 102 | if (i < this.audioSamples.length) { 103 | playNext() 104 | } 105 | else { 106 | this.showStopped() 107 | } 108 | } 109 | this.audioSamples[i].audio.addEventListener("ended", this.playingSoundSetListener) 110 | this.audioSamples[i].div.onclick() 111 | this.playingSample = this.audioSamples[i] 112 | } 113 | playNext() 114 | } 115 | -------------------------------------------------------------------------------- /www/js/sources.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "OpenMusic.Gallery", 4 | "url": "https://openmusic.gallery/data/?type=SOUNDSET" 5 | }, 6 | { 7 | "name": "Shiny Happy Kits", 8 | "url": "https://mikehelland.github.io/omg-sounds/drums/shiny_happy_kits.json" 9 | }, 10 | { 11 | "name": "MusyngKite", 12 | "url": "https://mikehelland.github.io/omg-sounds/soundfonts/MusyngKite.json" 13 | }, 14 | { 15 | "name": "FluidR3", 16 | "url": "https://mikehelland.github.io/omg-sounds/soundfonts/FluidR3.json" 17 | }, 18 | { 19 | "name": "FatBoy", 20 | "url": "https://mikehelland.github.io/omg-sounds/soundfonts/FatBoy.json" 21 | } 22 | ] -------------------------------------------------------------------------------- /www/js/webmidi.js: -------------------------------------------------------------------------------- 1 | function OMGMIDI() { 2 | this.parts = [] 3 | if (navigator.requestMIDIAccess) { 4 | navigator.requestMIDIAccess().then(midi => { 5 | this.midi = midi; 6 | midi.inputs.forEach(entry => entry.onmidimessage = event => this.onMessage(event)); 7 | }, 8 | e => { 9 | console.log( "Failed to get MIDI access", e ); 10 | }) 11 | } 12 | } 13 | 14 | 15 | OMGMIDI.prototype.onMessage = function (event) { 16 | 17 | var channel = (event.data[0] & 0x0f) + 1; 18 | switch (event.data[0] & 0xf0) { 19 | case 0x90: 20 | if (event.data[2]!=0) { // if velocity != 0, this is a note-on message 21 | this.onnoteon(event.data[1], event.data[2], channel); 22 | return; 23 | } 24 | // if velocity == 0, fall thru: it's a note-off. MIDI's weird, y'all. 25 | case 0x80: 26 | this.onnoteoff(event.data[1], channel); 27 | return; 28 | case 0xb0: 29 | switch (event.data[1]) { 30 | case 24: 31 | this.onplay(); 32 | return; 33 | case 23: 34 | this.onstop(); 35 | return; 36 | default: 37 | this.onmessage(event.data[1], event.data[2], channel); 38 | } 39 | return; 40 | case 0xE0: 41 | this.onmessage("pitchbend", event.data[2], channel); 42 | return; 43 | } 44 | } 45 | 46 | OMGMIDI.prototype.onnoteon = function (note) {console.log(note)} 47 | OMGMIDI.prototype.onnoteoff = function (note) {console.log(note)} 48 | OMGMIDI.prototype.onmessage = function (control, value) {console.log(control, value)} 49 | OMGMIDI.prototype.onplay = function () {} 50 | OMGMIDI.prototype.onstop = function () {} 51 | 52 | 53 | OMGMIDI.prototype.listInputsAndOutputs = function () { 54 | for (var input in this.midi.inputs) { 55 | console.log( "Input port [type:'" + input.type + "'] id:'" + input.id + 56 | "' manufacturer:'" + input.manufacturer + "' name:'" + input.name + 57 | "' version:'" + input.version + "'" ); 58 | } 59 | 60 | for (var output in this.midi.outputs) { 61 | console.log( "Output port [type:'" + output.type + "'] id:'" + output.id + 62 | "' manufacturer:'" + output.manufacturer + "' name:'" + output.name + 63 | "' version:'" + output.version + "'" ); 64 | } 65 | } 66 | 67 | 68 | OMGMIDI.prototype.onnoteoff = function (noteNumber, channel) { 69 | if (!this.player) return 70 | 71 | for (var part of this.parts) { 72 | if (!(part.midiChannel === channel || part.midiChannel === "All")) { 73 | return; 74 | } 75 | if (part.data.surface.url === "PRESET_SEQUENCER") { 76 | return 77 | } 78 | 79 | for (var i = 0; i< part.activeMIDINotes.length; i++) { 80 | if (part.activeMIDINotes[i].scaledNote === noteNumber) { 81 | part.activeMIDINotes.splice(i, 1); 82 | break; 83 | } 84 | } 85 | if (part.activeMIDINotes.length === 0) { 86 | this.player.endLiveNotes(part); 87 | } 88 | else if (i === 0 || 89 | (this.player.playing && part.activeMIDINotes.autobeat > 0)) { 90 | this.player.playLiveNotes(part.activeMIDINotes, part, 0); 91 | } 92 | } 93 | }; 94 | 95 | OMGMIDI.prototype.onnoteon = function (noteNumber, velocity, channel) { 96 | if (!this.player) return 97 | 98 | for (part of this.parts) { 99 | if (!(part.midiChannel === channel || part.midiChannel === "All")) { 100 | return; 101 | } 102 | var note = {beats: 0.25, scaledNote: noteNumber}; 103 | part.activeMIDINotes.splice(0, 0, note); 104 | if (!part.data.soundSet.chromatic) { 105 | note.note = noteNumber % part.soundSet.data.length; 106 | } 107 | else { 108 | /* 109 | //todo this looks horribly inneficient 110 | for (var i = 0; i < part.mm.frets.length; i++) { 111 | if (part.mm.frets[i].note === noteNumber) { 112 | note.note = i - part.mm.frets.rootNote; 113 | break; 114 | } 115 | if (part.mm.frets[i].note > noteNumber) { 116 | note.note = i - part.mm.frets.rootNote - 0.5; 117 | break; 118 | } 119 | } 120 | */ 121 | } 122 | if (part.data.surface.url === "PRESET_SEQUENCER") { 123 | this.player.playSound(part.data.tracks[note.note].sound, part, 124 | part.data.tracks[note.note].audioParams, velocity / 120) 125 | //todo omglive? 126 | /*if (tg.omglive && tg.omglive.socket) { 127 | tg.omglive.sendPlaySound(note.note, velocity / 120, part) 128 | }*/ 129 | } 130 | else { 131 | this.player.playLiveNotes(part.activeMIDINotes, part, 0); 132 | //tg.player.noteOn(note, part, velocity); 133 | } 134 | 135 | } 136 | }; 137 | 138 | /*tg.onmidiplay = function () { 139 | if (!tg.player.playing) { 140 | tg.player.play(); 141 | } 142 | }; 143 | 144 | tg.onmidistop = function () { 145 | if (tg.player.playing) { 146 | tg.player.stop(); 147 | } 148 | }; 149 | 150 | 151 | tg.onmidimessage = function (control, value, channel) { 152 | if (control === 91) { 153 | value = Math.floor(value / 128 * 4); 154 | if (value === 1) value = 4; 155 | else if (value === 3) value = 1; 156 | tg.midiParts.forEach(part => { 157 | if (!(part.midiChannel === channel || part.midiChannel === "All")) { 158 | return; 159 | } 160 | part.activeMIDINotes.autobeat = value; 161 | }); 162 | } 163 | else if (control === 7) { 164 | tg.midiParts.forEach(part => { 165 | if (!(part.midiChannel === channel || part.midiChannel === "All")) { 166 | return; 167 | } 168 | part.data.audioParams.gain = 1.5 * Math.pow(value / 127, 2); 169 | part.gain.gain.value = part.data.audioParams.gain; 170 | tg.song.partMuteChanged(part); 171 | }); 172 | } 173 | else if (control === 10) { 174 | tg.midiParts.forEach(part => { 175 | if (!(part.midiChannel === channel || part.midiChannel === "All")) { 176 | return; 177 | } 178 | part.data.audioParams.pan = (value - 64) / 64; 179 | part.panner.pan.value = part.data.audioParams.pan; 180 | tg.song.partMuteChanged(part); 181 | }); 182 | } 183 | else if (control === "pitchbend" || control === 5) { 184 | if (value === 64) { 185 | value = 1; 186 | } 187 | else if (value < 64) { 188 | value = value / 64 / 2 + 0.5; 189 | } 190 | else { 191 | value = 1 + (value - 64) / 63; 192 | } 193 | tg.midiParts.forEach(part => { 194 | if (!(part.midiChannel === channel || part.midiChannel === "All")) { 195 | return; 196 | } 197 | part.data.audioParams.warp = value; 198 | //part.panner.warp.value = part.data.audioParams.warp; 199 | if (part.osc) { 200 | part.osc.frequency.value = part.baseFrequency * value; 201 | } 202 | tg.song.partMuteChanged(part); 203 | }); 204 | } 205 | else if (control === 71) { 206 | tg.song.gain = value / 127 * 1.5; 207 | tg.song.postFXGain.gain.value = tg.song.gain; 208 | } 209 | else if (control === 74) { 210 | tg.song.data.beatParams.bpm = Math.round(value / 127 * 200 + 20); 211 | tg.song.beatsChanged(); 212 | } 213 | }; 214 | */ 215 | -------------------------------------------------------------------------------- /www/piano.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /www/playlist.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | OpenMusic.Gallery 14 | 15 | 16 |
17 |
18 |
19 | 20 |

Playlist:

21 | 22 | 23 |
24 |
25 | 26 |
27 | 28 | 29 | 30 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /www/sound-resources.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 | 15 |
16 | 17 | OpenMusic.Gallery 18 | 19 | 20 |
21 |
22 | 23 |
24 | 25 |

Websites with Sound files

26 | 27 |

Find the sound you want on one of these sites, 28 | then copy and paste into the SoundSet Editor 29 | or directly into OMG Creator. 30 |

31 | 32 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /www/soundset-editor-batch.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 |
16 | 17 | OpenMusic.Gallery 18 | 19 | 20 |
21 |
22 | 23 |
24 | 25 |

Add/Edit a Sound Set

26 | Sound Set Name: 27 |
28 | 29 | 30 |
31 | Default Surface: 36 | Chromatic 37 | Bottom Note: 38 | 62 | 63 |
64 | URL Prefix 65 | URL Postfix 66 | 67 |
68 |
69 | Sound 1 Name: 70 | 71 | 72 | 73 | URL: 74 | 75 | 76 |
77 | 78 |
79 | Sound 2 Name: 80 | 81 | 82 | 83 | URL: 84 | 85 | 86 |
87 | 88 |
89 | Sound 3 Name: 90 | 91 | 92 | 93 | URL: 94 | 95 | 96 |
97 | 98 |
99 | Sound 4 Name: 100 | 101 | 102 | 103 | URL: 104 | 105 | 106 |
107 | 108 |
109 | Sound 5 Name: 110 | 111 | 112 | 113 | URL: 114 | 115 | 116 |
117 | 118 |
119 | Sound 6 Name: 120 | 121 | 122 | 123 | URL: 124 | 125 | 126 |
127 | 128 |
129 | Sound 7 Name: 130 | 131 | 132 | 133 | URL: 134 | 135 | 136 |
137 | 138 |
139 | Sound 8 Name: 140 | 141 | 142 | 143 | URL: 144 | 145 | 146 |
147 |
148 | 149 | 150 | 151 |
Or Batch Input:
152 | 153 |
154 | 155 | 156 |
157 | 158 | 159 | 162 | 163 | 164 | 282 | 283 | 284 | 285 | -------------------------------------------------------------------------------- /www/soundset-editor.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 37 | 38 | 39 | 40 | 41 |
42 | 43 | OpenMedia.Gallery 44 | 45 |
46 |
47 | 48 |
49 | 50 |

Add/Edit a Sound Set

51 | 52 |
53 | Drop Files Here To Upload 54 |
55 | 56 | 57 | Sound Set Name: 58 |
59 | 60 | 61 |
62 | Default Surface: 67 | Chromatic 68 | Bottom Note: 69 | 93 | 94 |
95 | URL Prefix 96 | URL Postfix 97 | 98 | 99 |
100 |
101 |
102 | 103 | Add Sound 104 |
105 | 106 | 107 | 108 |
109 | 110 | 111 | 112 | 113 | 114 | 115 | 372 | 373 | 374 | 375 | 385 | 386 | 387 | 388 | -------------------------------------------------------------------------------- /www/soundset.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | OpenMedia.Gallery 20 | 21 | 22 |
23 |
24 | 25 |
26 | 27 |

Sound Set:

28 | 29 |
30 | 31 |
32 |
33 | 34 |
35 | 36 | 37 | 39 | 40 | 41 | 42 | 44 | 46 | 48 | 50 | 51 |
52 | 53 | 54 | 55 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /www/sources.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "OpenMedia.Gallery", 4 | "url": "https://openmedia.gallery/data/?type=SOUNDSET" 5 | }, 6 | { 7 | "name": "Shiny Happy Kits", 8 | "url": "https://mikehelland.github.io/omg-sounds/drums/shiny_happy_kits.json" 9 | }, 10 | { 11 | "name": "MusyngKite", 12 | "url": "https://mikehelland.github.io/omg-sounds/soundfonts/MusyngKite.json" 13 | }, 14 | { 15 | "name": "FluidR3", 16 | "url": "https://mikehelland.github.io/omg-sounds/soundfonts/FluidR3.json" 17 | }, 18 | { 19 | "name": "FatBoy", 20 | "url": "https://mikehelland.github.io/omg-sounds/soundfonts/FatBoy.json" 21 | } 22 | ] --------------------------------------------------------------------------------